Graphes - énoncé#

Links: notebook, html, PDF, python, slides, GitHub

Ce notebook introduit matplotlib et d’autres modules Python qui permettent de tracer des graphes et bâtis sur la même logique que matplotlib.

Pour avoir des graphiques inclus dans le notebook, il faut ajouter cette ligne et l’exécuter en premier.

%matplotlib inline

On change le style pour un style plus moderne, celui de ggplot :

import matplotlib.pyplot as plt
plt.style.use('ggplot')
from jyquickhelper import add_notebook_menu
add_notebook_menu()

Données#

Pour tous les exemples qui suivent, on utilise les résultat élection présidentielle de 2012. Si vous n’avez pas le module actuariat_python, il vous suffit de recopier le code de la fonction elections_presidentielles qui utilise la fonction read_excel. La fonction utilise des données stockées localement afin que le code ci-dessous fonctionne toujours même si le format des données change sur le site data.gouv.fr.

from actuariat_python.data import elections_presidentielles
dict_df = elections_presidentielles(local=True, agg="dep")
list(dict_df.keys())
['circ1', 'circ2', 'dep1', 'dep2']
dict_df["dep1"].head()
Code du département Libellé du département Code de la circonscription Inscrits Votants Exprimés Blancs et nuls Nathalie ARTHAUD (LO) Philippe POUTOU (NPA) Jean-Luc MELENCHON (FG) François HOLLANDE (PS) Eva JOLY (EELV) François BAYROU (MODEM) Nicolas SARKOZY (UMP) Nicolas DUPONT-AIGNAN (DLR) Marine LE PEN (FN) Jacques CHEMINADE (SP)
0 1 AIN 15 393808 327812 321359 6453 1794 3323 30898 73096 7268 32650 97722 7208 66540 860
1 2 AISNE 15 376068 303140 297944 5196 2490 3860 30360 80751 3455 19895 72090 5853 78452 738
2 3 ALLIER 6 256275 211009 205950 5059 1482 2584 27969 61131 3232 17814 49477 4068 37736 457
3 4 ALPES-DE-HAUTE-PROVENCE 3 123933 102899 100788 2111 487 1394 15269 24551 2933 7483 25668 1845 20875 283
4 5 HAUTES-ALPES 3 106865 88619 86777 1842 488 1152 12175 21248 3147 8559 22655 1782 15359 212
dict_df["dep2"].head()
Code du département Libellé du département Code de la circonscription Inscrits Votants Exprimés Blancs et nuls François HOLLANDE (PS) Nicolas SARKOZY (UMP)
0 979 SAINT-BARTHELEMY et SAINT-MARTIN 1 22686 9907 9492 415 3851 5641
1 01 AIN 15 393866 326587 307074 19513 131333 175741
2 02 AISNE 15 376073 302076 281020 21056 147260 133760
3 03 ALLIER 6 256211 211132 196208 14924 111615 84593
4 04 ALPES-DE-HAUTE-PROVENCE 3 123895 103581 96942 6639 49498 47444

On corrige le code du département 01 –> 1.

def cleandep(s):
    if isinstance(s, str):
         r = s.lstrip('0')
    else:
        r = str(s)
    return r
dict_df["dep1"]["Code du département"] = dict_df["dep1"]["Code du département"].apply(cleandep)
dict_df["dep2"]["Code du département"] = dict_df["dep2"]["Code du département"].apply(cleandep)
deps = dict_df["dep1"].merge(dict_df["dep2"],
                                       on="Code du département",
                                       suffixes=("T1", "T2"))
deps.columns
Index(['Code du département', 'Libellé du départementT1',
       'Code de la circonscriptionT1', 'InscritsT1', 'VotantsT1', 'ExprimésT1',
       'Blancs et nulsT1', 'Nathalie ARTHAUD (LO)', 'Philippe POUTOU (NPA)',
       'Jean-Luc MELENCHON (FG)', 'François HOLLANDE (PS)T1',
       'Eva JOLY (EELV)', 'François BAYROU (MODEM)', 'Nicolas SARKOZY (UMP)T1',
       'Nicolas DUPONT-AIGNAN (DLR)', 'Marine LE PEN (FN)',
       'Jacques CHEMINADE (SP)', 'Libellé du départementT2',
       'Code de la circonscriptionT2', 'InscritsT2', 'VotantsT2', 'ExprimésT2',
       'Blancs et nulsT2', 'François HOLLANDE (PS)T2',
       'Nicolas SARKOZY (UMP)T2'],
      dtype='object')
deps["rHollandeT1"] = deps['François HOLLANDE (PS)T1'] / (deps["VotantsT1"] - deps["Blancs et nulsT1"])
deps["rSarkozyT1"] = deps['Nicolas SARKOZY (UMP)T1'] / (deps["VotantsT1"] - deps["Blancs et nulsT1"])
deps["rNulT1"] = deps["Blancs et nulsT1"] / deps["VotantsT1"]
deps["rHollandeT2"] = deps["François HOLLANDE (PS)T2"] / (deps["VotantsT2"] - deps["Blancs et nulsT2"])
deps["rSarkozyT2"] = deps['Nicolas SARKOZY (UMP)T2'] / (deps["VotantsT2"] - deps["Blancs et nulsT2"])
deps["rNulT2"] = deps["Blancs et nulsT2"] / deps["VotantsT2"]
data = deps[["Code du département", "Libellé du départementT1",
             "VotantsT1", "rHollandeT1", "rSarkozyT1", "rNulT1",
             "VotantsT2", "rHollandeT2", "rSarkozyT2", "rNulT2"]]
data_elections = data # parfois data est remplacé dans la suite
data.head()
Code du département Libellé du départementT1 VotantsT1 rHollandeT1 rSarkozyT1 rNulT1 VotantsT2 rHollandeT2 rSarkozyT2 rNulT2
0 1 AIN 327812 0.227459 0.304090 0.019685 326587 0.427692 0.572308 0.059748
1 2 AISNE 303140 0.271027 0.241958 0.017141 302076 0.524020 0.475980 0.069704
2 3 ALLIER 211009 0.296824 0.240238 0.023975 211132 0.568861 0.431139 0.070686
3 4 ALPES-DE-HAUTE-PROVENCE 102899 0.243591 0.254673 0.020515 103581 0.510594 0.489406 0.064095
4 5 HAUTES-ALPES 88619 0.244858 0.261071 0.020786 89405 0.508935 0.491065 0.067390
deps.to_excel("deps.xlsx")
dict_df["dep1"].to_excel("T1.xlsx")
dict_df["dep2"].to_excel("T2.xlsx")

De pandas à matplotlib#

Lorsqu’on construit un graphique avec des données stockées dans un DataFrame, on suit généralement le processus suivant :

plot#

La méthode plot permet de faire la plupart des graphiques standards (voir Plotting).

data.plot(x="Libellé du départementT1", y=["rHollandeT2", "rSarkozyT2"], figsize=(16,5));
../_images/seance6_graphes_enonce_18_0.png
data.plot(x="Libellé du départementT1", y=["rHollandeT2", "rSarkozyT2"],
          figsize=(16,5), kind="bar", stacked=True);
../_images/seance6_graphes_enonce_19_0.png
data.plot(x="rHollandeT1", y="rHollandeT2", figsize=(16,5),
          kind="scatter", label="s1", title="correlation");
../_images/seance6_graphes_enonce_20_0.png

superposition#

La méthode plot retourne un objet de type Axes. On peut superposer plusieurs courbes sur le même graphique en s’assurant que la seconde courbe utilise le même objet.

ax = data.plot(x="rHollandeT1", y="rHollandeT2", figsize=(16,5),
               kind="scatter", label="H", title="correlation")
data.plot(x="rSarkozyT1", y="rSarkozyT2", kind="scatter", label="S", ax=ax, c="red");
../_images/seance6_graphes_enonce_22_0.png

On ajoute une ligne avec la méthode Axes.plot ou du text avec text :

ax = data.plot(x="rHollandeT1", y="rHollandeT2", figsize=(16,5),
               kind="scatter", label="H", title="correlation")
data.plot(x="rSarkozyT1", y="rSarkozyT2", kind="scatter", label="S", ax=ax, c="red")
ax.plot([0.2,0.7], [0.2,0.7], "g--")
ax.text(0.5, 0.5, "rien au dessous", weight="bold", rotation="-30");
../_images/seance6_graphes_enonce_24_0.png

plusieurs graphes sur la même figure#

pandas crée une Figure de façon implicite avec un seul graphe. Pour créer plusieurs graphes, il faut créer ce type d’objet en précisant qu’il y aura plusieurs Axes avec la fonction subplots et les transmettre à pandas. On peut également partager l’axe des X ou l’axe des Y.

import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 2, figsize=(16,5), sharey=True)
data.plot(x="rHollandeT1", y="rHollandeT2", figsize=(16,5),
          kind="scatter", label="H", ax=axes[0])
data.plot(x="rSarkozyT1", y="rSarkozyT2", kind="scatter",
          label="S", ax=axes[1], c="red")
axes[0].plot([0.2,0.7], [0.2,0.7], "g--")
axes[1].plot([0.2,0.7], [0.2,0.7], "g--");
../_images/seance6_graphes_enonce_26_0.png

matplotlib sans pandas#

On peut se passer de pandas et s’inspirer d’un graphe de la gallerie pour ajouter des points dépendants du nombre de votants scatter_demo et ajouter une légende manuellement avec la méthode legend.

import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 1, figsize=(16,5))
c = axes.scatter(x=data["rHollandeT1"],
            y=data["rHollandeT2"],
            s=data["VotantsT1"]/5000, alpha=0.5)
axes.plot([0.2,0.7], [0.2,0.7], "g--")
axes.legend( (c,), ("H",) );
../_images/seance6_graphes_enonce_28_0.png

Pandas et graphes prêts à l’emploi#

histogrammes#

avec hist

data.hist(figsize=(8, 8));
../_images/seance6_graphes_enonce_31_0.png

Le paramètre figsize permettrait de modifier la taille du graphique.

correlations#

avec scatter_matrix

from pandas.plotting import scatter_matrix
scatter_matrix(data, alpha=0.2, figsize=(14, 14), diagonal='kde')
print("-");
-
../_images/seance6_graphes_enonce_34_1.png

cartes avec cartopy#

Les coordonnées sur une carte se font avec des coordonnées géographiques : longitude et latitude. La distance entre deux lieux géographiques se calcule grâce à la distance de Haversine. Les graphes se font avec cartopy.

une carte simple#

On la choisit centrée sur la France. La carte se dessine avec matplotlib auquel cartopy ajoute un système de projection différent. Comme elle accepte un argument ax, il est possible de changer sa taille ou de la juxtaposer à côté d’un autre graphe. Le module cartopy ne contient pas toutes les informations sur le territoire français et certaines options ne semble pas avoir d’effet.

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt

fig = plt.figure(figsize=(7,7))
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
ax.set_extent([-5, 10, 42, 52])

ax.add_feature(cfeature.OCEAN.with_scale('50m'))
# ax.add_feature(cfeature.COASTLINE)
# ax.add_feature(cfeature.RIVERS)  # pas d'effet, cartopy ne connaît pas les rivières en France
# ax.add_feature(cfeature.BORDERS, linestyle=':')
# cette instruction télécharge un fichier (10m=14Mo, 50m, 110m)
# il faut la résolution 10m pour la France
ax.add_feature(cfeature.STATES.with_scale('10m'))
ax.set_title('France');
../_images/seance6_graphes_enonce_37_0.png

La méthode with_scale propose trois résolution 10m, 50m, 110m. La module cartopy n’inclut que la résolution 110m, le reste doit être téléchargé.

exercice 1 : centrer la carte de la France#

ajouter du texte ou une marque#

Sur une carte, on veut la plupart du temps ajouter du texte. On reprend le début de ce code qu’on place dans une fonction, puis on place Paris. On utilise pour cela les fonctions standard de matplotlib mais on convertit les coordonnées géographiques en coordonnées relatives au graphe (donc dans un repère différent).

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt

def carte_france(figsize=(7, 7)):
    fig = plt.figure(figsize=figsize)
    ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree())
    ax.set_extent([-5, 10, 42, 52])

    ax.add_feature(cfeature.OCEAN.with_scale('50m'))
    ax.add_feature(cfeature.RIVERS.with_scale('50m'))
    ax.add_feature(cfeature.BORDERS.with_scale('50m'), linestyle=':')
    ax.set_title('France');
    return ax

carte_france();
../_images/seance6_graphes_enonce_42_0.png
import matplotlib.pyplot as plt

ax = carte_france()

lon = 2.3488000
lat = 48.853410
ax.plot([lon], [lat], 'ro', markersize=6)
ax.text(lon, lat, "Paris");
../_images/seance6_graphes_enonce_43_0.png

On connaît rarement les coordonnées de chaque ville mais un moteur de recherche donne rapidement des pistes pour trouver ces données. Il faut néanmoins s’assurer que le licence autorise ce qu’on l’intention de faire avec :

Liste des villes de France en SQL, CSV ou XML

from pyensae.datasource import download_data
download_data("villes_france.csv", url="http://sql.sh/ressources/sql-villes-france/")
'villes_france.csv'
cols = ["ncommune", "numero_dep", "slug", "nom", "nom_simple", "nom_reel",
        "nom_soundex", "nom_metaphone", "code_postal",
        "numero_commune", "code_commune", "arrondissement", "canton",
        "pop2010", "pop1999", "pop2012",
        "densite2010", "surface", "superficie", "dlong", "dlat", "glong",
        "glat", "slong", "slat", "alt_min", "alt_max"]
import pandas
df = pandas.read_csv("villes_france.csv", header=None,low_memory=False, names=cols)
df.head()
ncommune numero_dep slug nom nom_simple nom_reel nom_soundex nom_metaphone code_postal numero_commune ... surface superficie dlong dlat glong glat slong slat alt_min alt_max
0 1 01 ozan OZAN ozan Ozan O250 OSN 01190 284 ... 93 6.60 4.91667 46.3833 2866.0 51546.0 45456.0 462330.0 170.0 205.0
1 2 01 cormoranche-sur-saone CORMORANCHE-SUR-SAONE cormoranche sur saone Cormoranche-sur-Saône C65652625 KRMRNXSRSN 01290 123 ... 107 9.85 4.83333 46.2333 2772.0 51379.0 44953.0 461427.0 168.0 211.0
2 3 01 plagne-01 PLAGNE plagne Plagne P425 PLKN 01130 298 ... 20 6.20 5.73333 46.1833 3769.0 51324.0 54342.0 461131.0 560.0 922.0
3 4 01 tossiat TOSSIAT tossiat Tossiat T230 TST 01250 422 ... 138 10.17 5.31667 46.1333 3309.0 51268.0 51854.0 460828.0 244.0 501.0
4 5 01 pouillat POUILLAT pouillat Pouillat P430 PLT 01250 309 ... 14 6.23 5.43333 46.3333 3435.0 51475.0 52542.0 461938.0 333.0 770.0

5 rows × 27 columns

exercice 2 : placer les plus grandes villes de France sur la carte#

départements#

Pour dessiner des formes sur une carte, il faut connaître les coordonnées de ces formes. L’article suivant Matplotlib Basemap tutorial 10: Shapefiles Unleached, continued permet de dessiner les départements belges. On va s’en inspirer pour dessiner les départements français. La première chose à faire est de récupérer des données géographiques. Une façon simple de les trouver est d’utiliser un moteur de recherche avec le mot clé shapefile inclus dedans : c’est le format du fichier. shapefile france permet d’obtenir quelques sources. En voici d’autres :

La première chose à vérifier est la licence associées aux données : on ne peut pas en faire ce qu’on veut. Pour cet exemple, j’ai choisi la première source de données, GADM. La licence n’est pas précisée explicitement (on peut trouver happy to share sur le site, la page wikipedia GADM précise : GADM is not freely available for commercial use. The GADM project created the spatial data for many countries from spatial databases provided by national governments, NGO, and/or from maps and lists of names available on the Internet (e.g. from Wikipedia). C’est le choix que j’avais fait en 2015 mais l’accès à ces bases a probablement changé car l’accès est restreint. J’ai donc opté pour les bases accessibles depuis data.gouv.fr. Leur seul inconvénient est que les coordonnées sont exprimées dans une projection de type Lambert 93. Cela nécessite une conversion.

from pyensae.datasource import download_data
try:
    download_data("GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01.7z",
              website="https://wxs-telechargement.ign.fr/oikr5jryiph0iwhw36053ptm/telechargement/inspire/" + \
              "GEOFLA_THEME-DEPARTEMENTS_2015_2$GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/file/")
except Exception as e:
    # au cas le site n'est pas accessible
    download_data("GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01.7z", website="xd")
from pyquickhelper.filehelper import un7zip_files
try:
    un7zip_files("GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01.7z", where_to="shapefiles")
    departements = 'shapefiles/GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/' + \
                   'GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT/DEPARTEMENT.shp'
except FileNotFoundError as e:
    # Il est possible que cette instruction ne fonctionne pas.
    # Dans ce cas, on prendra une copie de ce fichier.
    import warnings
    warnings.warn("Plan B parce que " + str(e))
    download_data("DEPARTEMENT.zip")
    departements = "DEPARTEMENT.shp"

import os
if not os.path.exists(departements):
    raise FileNotFoundError("Impossible de trouver '{0}'".format(departements))

La license accompagne les données : ce produit est téléchargeable et utilisable gratuitement sous licenceEtalab. Pour un usage commercial, il faut faire attentation à la license associée aux données. Le seul inconvénient des données GEOFLA est que certaines sont données dans le système de coordonnées Lambert 93 (voir aussi Cartographie avec R).

shp = departements
import shapefile
r = shapefile.Reader(shp)
shapes = r.shapes()
records = r.records()
len(shapes), len(records)
(96, 96)
r.bbox
[99217.1, 6049646.300000001, 1242417.2, 7110480.100000001]

On regarde une zone en particulier mais on réduit la quantité de données affichées :

d = shapes[0].__dict__.copy()
d["points"] = d["points"][:10]
d
{'shapeType': 5,
 'points': [(701742.0, 6751181.100000001),
  (701651.9, 6751166.9),
  (701552.0, 6751162.7),
  (700833.7000000001, 6751313.7),
  (700669.4, 6751380.0),
  (700475.4, 6751476.600000001),
  (700400.7000000001, 6751517.2),
  (700098.3, 6751789.600000001),
  (699993.8, 6751845.4),
  (699874.1000000001, 6751876.4)],
 'parts': [0],
 'bbox': [688654.4, 6690595.300000001, 800332.3, 6811114.5]}

350 départements, sûr ?

records[0], records[1]
(Record #0: ['DEPARTEM0000000000000004', '89', 'YONNE', '024', 'AUXERRE', 742447, 6744261, 748211, 6750855, '27', 'BOURGOGNE-FRANCHE-COMTE'],
 Record #1: ['DEPARTEM0000000000000028', '69', 'RHONE', '381', 'LYON', 842221, 6520526, 832095, 6530600, '84', 'AUVERGNE-RHONE-ALPES'])
len(set([r[6] for r in records]))
96

Puis je récupère le code final (toujours à Matplotlib Basemap tutorial 10: Shapefiles Unleached, continued) en l’adaptant pour la France. Petite astuce, on utilie la fonction lambert932WGPS du module ensae_teaching_cs. On recopie le code ici :

import math


def lambert932WGPS(lambertE, lambertN):

    class constantes:
        GRS80E = 0.081819191042816
        LONG_0 = 3
        XS = 700000
        YS = 12655612.0499
        n = 0.7256077650532670
        C = 11754255.4261

    delX = lambertE - constantes.XS
    delY = lambertN - constantes.YS
    gamma = math.atan(-delX / delY)
    R = math.sqrt(delX * delX + delY * delY)
    latiso = math.log(constantes.C / R) / constantes.n
    sinPhiit0 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * math.sin(1)))
    sinPhiit1 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit0))
    sinPhiit2 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit1))
    sinPhiit3 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit2))
    sinPhiit4 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit3))
    sinPhiit5 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit4))
    sinPhiit6 = math.tanh(latiso + constantes.GRS80E * math.atanh(constantes.GRS80E * sinPhiit5))

    longRad = math.asin(sinPhiit6)
    latRad = gamma / constantes.n + constantes.LONG_0 / 180 * math.pi

    longitude = latRad / math.pi * 180
    latitude = longRad / math.pi * 180

    return longitude, latitude

lambert932WGPS(99217.1, 6049646.300000001), lambert932WGPS(1242417.2, 7110480.100000001)
((-4.1615802638173065, 41.303505287589545),
 (10.699505053975292, 50.85243395553585))
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
ax = carte_france((8,8))

from matplotlib.collections import LineCollection
import shapefile
import geopandas
from shapely.geometry import Polygon
from shapely.ops import cascaded_union, unary_union

shp = departements
r = shapefile.Reader(shp)
shapes = r.shapes()
records = r.records()

polys = []
for i, (record, shape) in enumerate(zip(records, shapes)):
    # les coordonnées sont en Lambert 93
    if i == 0:
        print(record, shape.parts)
    geo_points = [lambert932WGPS(x,y) for x, y in shape.points]
    if len(shape.parts) == 1:
        # Un seul polygone
        poly = Polygon(geo_points)
    else:
        # Il faut les fusionner.
        ind = list(shape.parts) + [len(shape.points)]
        pols = [Polygon(geo_points[ind[i]:ind[i+1]]) for i in range(0, len(shape.parts))]
        try:
            poly = unary_union(pols)
        except Exception as e:
            print("Cannot merge: ", record)
            print([_.length for _ in pols], ind)
            poly = Polygon(geo_points)
    polys.append(poly)

data = geopandas.GeoDataFrame(geometry=polys)
# cmap -> voir https://matplotlib.org/users/colormaps.html
data.plot(ax=ax, cmap='tab20', edgecolor='black');
# Ou pour définir des couleurs spécifiques.
# geopandas.plotting.plot_polygon_collection(ax, data['geometry'], data['colors'], values=None)
Record #0: ['DEPARTEM0000000000000004', '89', 'YONNE', '024', 'AUXERRE', 742447, 6744261, 748211, 6750855, '27', 'BOURGOGNE-FRANCHE-COMTE'] [0]
../_images/seance6_graphes_enonce_63_1.png

exercice 3 : résultats des élections par départements#

Ce n’est pas toujours évident !

seaborn#

seaborn propose des graphiques inéressants pour un statisticien. La gallerie en donne un bon aperçu. On retravaille peu les graphiques. Le code suivant montre les corrélations entre variables pairplot.

warning les warnings sont la plupart du temps dûs au fait que seaborn a été testé sur une version antérieure d’une de ses dépendances comme matplotlib et qu’il n’est pas encore à jour pour tenir compte des derniers développement.

import seaborn
seaborn.pairplot(data_elections);
../_images/seance6_graphes_enonce_67_0.png

Celui-ci est aussi intéressant : clustermap pour étudier les corrélations.

import seaborn
seaborn.set(font="monospace")

cmap = seaborn.diverging_palette(h_neg=210, h_pos=350, s=90, l=30, as_cmap=True)

seaborn.clustermap(data_elections.corr(), linewidths=.5, figsize=(13, 13), cmap=cmap);
../_images/seance6_graphes_enonce_69_0.png

bokeh#

bokeh propose des graphiques en javascript. La gallerie est moins fournie que celle de matplotib. Le principale avantage de bokeh est de proposer gaphiques zoomables (interactifs).

initialisation#

La première étape est de signifier que la sortie se fera dans un notebook.

from bokeh.plotting import output_notebook
output_notebook()
Loading BokehJS ...

premier graphe#

On utilise bokeh pour un simple graphique XY. on peut choisir les différentes options interactives, zoom … Voir tools.

from bokeh.plotting import figure, show

p = figure(title = "élections")
p.title.text = "élections"
p.circle(data_elections["rHollandeT1"], data_elections["rHollandeT2"], color="red", fill_alpha=0.2, size=10, legend="H")
p.circle(data_elections["rSarkozyT1"], data_elections["rSarkozyT2"], color="blue", fill_alpha=0.2, size=10, legend="S")
p.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
p.xaxis.axis_label = "tour 1"
p.yaxis.axis_label = "tour 2"
show(p)
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead

ajouter du texte#

Les différents éléments qu’on peut ajouter au graphe s’appelle des Glyphes. La documentation manque parfois de précision. Pour la fonction text, il faut préciser le texte à afficher sous forme de liste.

from bokeh.plotting import figure, show

p = figure(title = "élections")
p.title.text = "élections"
p.circle(data_elections["rHollandeT1"], data_elections["rHollandeT2"], color="red", fill_alpha=0.2, size=10, legend="H")
p.circle(data_elections["rSarkozyT1"], data_elections["rSarkozyT2"], color="blue", fill_alpha=0.2, size=10, legend="S")
p.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
p.xaxis.axis_label = "tour 1"
p.yaxis.axis_label = "tour 2"


def display_text(p, row):
    p.text(x=row["rHollandeT1"], y=row["rHollandeT2"], text=[row["Libellé du départementT1"]],
          text_font_size="8pt", color="black", text_align="left", text_baseline="middle", angle=0)
    return row["Libellé du départementT1"]

data_elections.apply(lambda row: display_text(p, row), axis=1)

show(p)
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead

composition de graphes#

Voir linked brushing.

from bokeh.plotting import figure, show, gridplot
size = 400

ph = figure(title = "élections", width=size, height=size)
ph.title.text = "élections"
ph.circle(data_elections["rHollandeT1"], data_elections["rHollandeT2"], color="red", fill_alpha=0.2, size=10, legend="H")
ph.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
ph.xaxis.axis_label = "tour 1"
ph.yaxis.axis_label = "tour 2"

ps = figure(title = "élections", width=size, height=size)
ps.title.text = "élections"
ps.circle(data_elections["rSarkozyT1"], data_elections["rSarkozyT2"], color="blue", fill_alpha=0.2, size=10, legend="S")
ps.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
ps.xaxis.axis_label = "tour 1"
ps.yaxis.axis_label = "tour 2"

p = gridplot([[ph, ps]])

show(p)
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead

interactions avec bokeh#

Tout est expliqué dans la page interaction.

from bokeh.models.widgets import Panel, Tabs
from bokeh.io import show
from bokeh.plotting import figure

ph = figure(title="élections")
ph.circle(data_elections["rHollandeT1"], data_elections["rHollandeT2"], color="red", fill_alpha=0.2, size=10, legend="H")
ph.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
ph.xaxis.axis_label = "tour 1"
ph.yaxis.axis_label = "tour 2"

ps = figure(title = "élections")
ps.title.text = "élections"
ps.circle(data_elections["rSarkozyT1"], data_elections["rSarkozyT2"], color="blue", fill_alpha=0.2, size=10, legend="S")
ps.line([0.2,0.7], [0.2,0.7], line_color="green", line_dash="dashed")
ps.xaxis.axis_label = "tour 1"
ps.yaxis.axis_label = "tour 2"

tab1 = Panel(child=ph, title="Hollande")
tab2 = Panel(child=ps, title="Sarkozy")

tabs = Tabs(tabs=[ tab1, tab2 ])

show(tabs)
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead
BokehDeprecationWarning: 'legend' keyword is deprecated, use explicit 'legend_label', 'legend_field', or 'legend_group' keywords instead

Si on connaît un peu de javascript, on peut créer des graphes qui peuvent interagir avec la souris à n’importe quel endroit du graphe.

interaction avec matplotlib (ou bokeh)#

exercice 4 : même code, widget différent#

autres options#

Il existe un grande nombre de modules permettant de dessiner. Les plus récents utilisent le javascript.

  • static

    • ggplot : aspect similare à matplotlib version ggplot mais la syntaxe est différente

  • Javascript

    • pygal : voir exemple plus bas, aspect réussi, le module prévoit une extension pour les carte, le résultat nécessite l’ajout de quelques scripts Javascript

    • mpld3 : créer un graphe avec la syntaxe Javascript et le transformer en Javascript

    • folium : Javascript + OpenStreetMap

html_pygal = """
<script type="text/javascript" src="http://kozea.github.com/pygal.js/javascripts/svg.jquery.js"></script>
<script type="text/javascript" src="http://kozea.github.com/pygal.js/javascripts/pygal-tooltips.js"></script>
{pygal_render}
"""

from IPython.core.display import HTML
import pygal
xy_chart = pygal.XY(stroke=False)
xy_chart.title = 'Elections 2012'
xy = list(zip(data_elections["rHollandeT1"], data_elections["rHollandeT2"]))
xy_chart.add('H', xy)
HTML(html_pygal.format(pygal_render=xy_chart.render().decode()))
Elections 20120.360.360.40.40.440.440.480.480.520.520.560.560.60.60.640.640.680.680.20.20.240.240.280.280.320.320.360.360.40.40.440.440.480.480.520.520.560.56Elections 20120.22745901: 0.427691696581.0647766051942407.55023461349210.2710274414: 0.5240196427157.53588768153753276.218512312604960.296824472: 0.5688605969202.81471748793922215.083193110457560.2435905068: 0.5105939634109.37870583566756294.52283268606950.2448575083: 0.5089349964111.6025410958509296.78463729870630.1921446979: 0.35686400619.081257791223354504.115384615384640.2598471487: 0.5344739544137.91228924254577261.965299151331240.289346381: 0.5188984103189.68920646254398283.200704897625540.3436094453: 0.6469461912284.9314882292195108.622752733234220.2279310664: 0.42630168781.89332776118067409.44534751695650.3040610498: 0.5625124908215.51632572983564223.738082393949530.2943817999: 0.544246979198.52735063445377248.64093992767930.2450954861: 0.4717058377112.02023854046952347.542176748614570.2933821918: 0.5311928218196.77284304770458266.438734015677770.3084466394: 0.5180154747223.21389256997236284.40448283684310.3279448484: 0.5882997589257.4370595761621188.580202831661670.2846826723: 0.5156611402181.50348642317812287.61433854394490.2676430912: 0.5404444289151.59569164452353253.825265670418050.4296742368: 0.6486280588435.99201592959423106.329725839475940.2778590727: 0.4845220526169.5267356040422330.0687887599560.3302260386: 0.5919094547261.440994112384183.65881107910360.3401543408: 0.6101681388278.8671047493355158.765262276418530.3208868145: 0.591355662245.04883077777768184.413841626240130.2628306315: 0.4808757424143.14888459607607335.040100056867230.2504986939: 0.4918420525121.50392408286245320.088837656147460.2465475746: 0.4754584072114.56893759888008342.42599363614860.2570867555: 0.4652706844133.06725965853144356.31574456610860.336956677: 0.5886362423273.25457983769303188.121447629068540.2410806983: 0.4880095439104.97350150059782325.314008248975140.3282833016: 0.5877897563258.0311110732751189.275530834168480.31861502: 0.5664088131241.0613874895372218.425909406186630.3175492669: 0.5660996845239.19078266702041218.84736946199480.2669087013: 0.5131409025150.30669394784496291.050383548272860.3176627647: 0.5571454684239.38999342035038231.055380518671480.2987088695: 0.5566048435206.12220342390205231.792458316519060.2810099775: 0.512271165175.05718925933752292.23616734743950.2784438552: 0.521245461170.55314311561494280.00077986417410.2460202605: 0.494249103113.64339836862091316.807109962286860.3275273756: 0.5698881114256.70431329313305213.682299040627360.2501102377: 0.4757464378120.82210754353578342.03329798660880.2645875715: 0.5050122963146.2326575481917302.132773399117470.2636337949: 0.5137946367144.55859315948732290.1590945411050.3175662576: 0.5635370249239.22060466449628222.341251712710740.2545099636: 0.459727615128.54448640285398363.87306177128880.3451301832: 0.6188924192287.60068049768853146.870741352721840.2691317444: 0.5135409093154.2085690839581290.50502169221910.251549814: 0.4995315135123.34884538169514309.60517017535560.2710428218: 0.4885272896157.5628832139739324.60812353122910.2703860137: 0.4990071182156.41005679414104310.3201209643330.2405223124: 0.4468609643103.99342511879745381.41521296112410.237487769: 0.455749636198.6672083527552369.29656390515290.2586588102: 0.4692814875135.82652294865477350.84749044533680.279223294: 0.5306282164171.9212106021703267.2085063781840.2339890088: 0.461998146792.5262006096088360.77746127218390.2828813552: 0.5173251854178.34182285177977285.34561031655230.2452890545: 0.4650352796112.3599890058706356.63669103121050.326434194: 0.5881212704254.78556591053663188.823550754017220.2796814818: 0.5287937639172.72541971235609269.709564679204960.2490406428: 0.4733560214118.94475954890072345.29234710050220.2426361408: 0.4711294378107.70360690887996348.32802963727930.2943817208: 0.5618435736198.52721181098605224.650071598683160.3308864131: 0.6046696171262.60008045796314166.261844052320270.2993037086: 0.571203481207.16626220401236211.888948721674860.3315941508: 0.6246555037263.84229846350917139.013459632639580.2596311801: 0.5058602498137.5332221133715300.976689387301240.1957599175: 0.36559510325.42667476225948492.21156997511940.1888947266: 0.3666556213.376923076923061490.7656809130560.2690870038: 0.4797769249154.13004051252685336.53820725366020.2638327783: 0.4963742183144.9078479745138313.90976753843620.2869166877: 0.5185616496185.42461997218203283.659838204494750.2812678242: 0.5266959464175.5097606016877272.56968988266020.2363860724: 0.470667203796.73351564644082348.958231000166850.2051714488: 0.398973035741.94575135321092446.704720114506760.3482559745: 0.5560344101293.0870549611268232.570176645922740.2940001524: 0.5494242986197.85748479486088241.582279119142980.2764595121: 0.4925310938167.07023329370008319.14941166913250.2731564701: 0.4570477056161.2727491567879367.526800251787340.3333603951: 0.5730718101266.9424023349598209.341703785836840.2837086077: 0.5440857882179.79381258636184248.86070444112680.3068977754: 0.5555442098220.4953334740937233.23850647706410.2763020762: 0.5125240293166.79390249462145291.89141691273880.1964659664: 0.373608318326.665928510694187481.28650200119250.2233315968: 0.435665582173.82035989354489396.67878793437170.2481172555: 0.4440166679117.32403433260615385.293073486327670.3198787089: 0.5716431017243.279408577325211.28957811354560.3590408325: 0.6399392027312.016588403223118.17594986395630.2468086779: 0.4906111046115.0272250764751321.76708898658540.2399627718: 0.4688413485103.0113219018644351.44756773667290.2600536551: 0.5052332051138.27474824133535301.831590340875830.3039023794: 0.5343166443215.23782819111605262.17977275073670.3015503263: 0.4948467982211.10951538954086315.99222359255120.3868349831: 0.6531908835360.80075409648975100.108855867461610.3293363765: 0.5647584825259.8794632657387220.675939316577970.3241095145: 0.5390821527250.7052990460707255.68256765203870.2831581923: 0.4694964979178.82772604226005350.55434934451680.5699612149: 0.7193677038682.2230769230779.8846153846153580.5198195119: 0.6843316045594.214589485001257.652178671278990.4261466909: 0.6204747063429.80048346042724144.713480646970370.5328673249: 0.7148632408617.116051195152916.025916129696610.3375142531: 0.6530612245274.2332349178825100.285630549117510.2677704469: 0.4057100716151.81922587698762437.5195712298540.3655059334: 0.4905494278323.3641037504895321.851178061315640.4827532386: 0.5606441129529.1560366191901226.28539396060250.324318908: 0.4694047691251.07282565902872350.679410710537870.2491065327: 0.3697442124119.06040915828333486.55475180912390.2228659974: 0.423966623573.00314198098302412.628929467622750.2599373534: 0.455932439138.0706159719668369.0473338349626H