Graphes - énoncé

Links: notebook, html ., PDF, python, slides ., presentation ., 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()
run previous cell, wait for 2 seconds

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))
<matplotlib.axes._subplots.AxesSubplot at 0x238d35edc88>
../_images/seance6_graphes_enonce_18_1.png
data.plot(x="Libellé du départementT1", y=["rHollandeT2", "rSarkozyT2"], figsize=(16,5), kind="bar", stacked=True)
<matplotlib.axes._subplots.AxesSubplot at 0x238d38c0b70>
../_images/seance6_graphes_enonce_19_1.png
data.plot(x="rHollandeT1", y="rHollandeT2", figsize=(16,5), kind="scatter", label="s1", title="correlation")
<matplotlib.axes._subplots.AxesSubplot at 0x238d3bdff28>
../_images/seance6_graphes_enonce_20_1.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")
print(type(ax))
data.plot(x="rSarkozyT1", y="rSarkozyT2", kind="scatter", label="S", ax=ax, c="red")
<class 'matplotlib.axes._subplots.AxesSubplot'>
<matplotlib.axes._subplots.AxesSubplot at 0x238d46cdf60>
../_images/seance6_graphes_enonce_22_2.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")
<matplotlib.text.Text at 0x238d4309d68>
../_images/seance6_graphes_enonce_24_1.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--")
[<matplotlib.lines.Line2D at 0x238d418e630>]
../_images/seance6_graphes_enonce_26_1.png

matplolib 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",) )
<matplotlib.legend.Legend at 0x238d49ed320>
../_images/seance6_graphes_enonce_28_1.png

Pandas et graphes prêts à l’emploi

histogrammes

avec hist

data.hist()
array([[<matplotlib.axes._subplots.AxesSubplot object at 0x00000238D49F15F8>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D4FF7550>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D500B128>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x00000238D50C5518>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D5116DA0>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D5116DD8>],
       [<matplotlib.axes._subplots.AxesSubplot object at 0x00000238D51F2978>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D526D710>,
        <matplotlib.axes._subplots.AxesSubplot object at 0x00000238D52D5160>]], dtype=object)
../_images/seance6_graphes_enonce_31_1.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 basemap

Je recommande la lecture de ce tutoriel Visualization: Mapping Global Earthquake Activity. Les exemples se font avec le module basemap. 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.

Alternative à basemap : cartopy.

une carte simple

On la choisit centrée sur la France. On crée la carte à l’aide de l’objet Basemap. Comme elle accepte un argument ax, il est possible de changer sa taille ou de la juxtaposer à côté d’un autre graphe. Les couleurs peuvent être être décrite en hexadécimale #RRVVBB (rouge, vert, bleu) ou on peut utiliser la palette des couleurs.

from mpl_toolkits.basemap import Basemap
import numpy

import matplotlib.pyplot as plt
fig, axes = plt.subplots(1, 1, figsize=(8,8))

m = Basemap(llcrnrlon=-5,llcrnrlat=40,urcrnrlon=20,urcrnrlat=56,
            resolution='i',projection='cass',lon_0=2.34,lat_0=48,
           ax=axes)
m.drawcoastlines()
m.drawcountries()
m.fillcontinents(color='lightgrey', lake_color='#AAAAFF')

m.drawparallels(numpy.arange(-40,61.,2.))
m.drawmeridians(numpy.arange(-20.,21.,2.))
m.drawmapboundary(fill_color='#BBBBFF')
<matplotlib.patches.Rectangle at 0x238d8e7b7f0>
../_images/seance6_graphes_enonce_37_1.png

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).

def carte_france():
    from mpl_toolkits.basemap import Basemap
    import numpy

    import matplotlib.pyplot as plt
    fig, axes = plt.subplots(1, 1, figsize=(8,8))

    m = Basemap(llcrnrlon=-5,llcrnrlat=40,urcrnrlon=20,urcrnrlat=56,
                resolution='i',projection='cass',lon_0=2.34,lat_0=48,
               ax=axes)
    m.drawcoastlines()
    m.drawcountries()
    m.fillcontinents(color='lightgrey', lake_color='#AAAAFF')

    m.drawparallels(numpy.arange(-40,61.,2.))
    m.drawmeridians(numpy.arange(-20.,21.,2.))
    m.drawmapboundary(fill_color='#BBBBFF')
    return m, axes
import matplotlib.pyplot as plt

m, ax = carte_france()

lon = 2.3488000
lat = 48.853410
x,y = m(lon, lat)  # la conversion opère ici
m.plot(x, y, 'ro', markersize=6)
ax.text(x, y, "Paris")
<matplotlib.text.Text at 0x238dac01048>
../_images/seance6_graphes_enonce_42_1.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

import pyensae
pyensae.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 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
un7zip_files("GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01.7z", where_to="shapefiles")
['shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/3_METADONNEES_PRODUIT/IGNF_GEOFLAr_2-1.xml',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/3_METADONNEES_PRODUIT/IGNF_GEOFLAr_2-1.html',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/4_METADONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/LISEZ-MOI.txt',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/5_SUPPLEMENTS_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/LISEZ_MOI.TXT',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/2_DESCRIPTIFS_PRODUIT/DC_GEOFLA_2-1.pdf',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/2_DESCRIPTIFS_PRODUIT/DL_vecteur.pdf',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/2_DESCRIPTIFS_PRODUIT/Supplements_Gratuits.pdf',
 '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.dbf',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT/LIMITE_DEPARTEMENT.dbf',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/2_DESCRIPTIFS_PRODUIT.md5',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/3_METADONNEES_PRODUIT.md5',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT.md5',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152.md5',
 '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.prj',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT/LIMITE_DEPARTEMENT.prj',
 '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',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT/LIMITE_DEPARTEMENT.shp',
 '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.shx',
 'shapefiles\GEOFLA_2-1_DEPARTEMENT_SHP_LAMB93_FXX_2015-12-01/GEOFLA/1_DONNEES_LIVRAISON_2015/GEOFLA_2-1_SHP_LAMB93_FR-ED152/DEPARTEMENT/LIMITE_DEPARTEMENT.shx']

La license accompagne les données : ce produit est téléchargeable et utilisable gratuitement sous licence `Etalab <https://www.etalab.gouv.fr/licence-ouverte-open-licence>`__. 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 = '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'
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
{'bbox': [688654.4, 6690595.300000001, 800332.3, 6811114.5],
 'parts': [0],
 '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)],
 'shapeType': 5}

350 départements, sûr ?

records[0], records[1]
(['DEPARTEM0000000000000004',
  '89',
  'YONNE',
  '024',
  'AUXERRE',
  742447,
  6744261,
  748211,
  6750855,
  '27',
  'BOURGOGNE-FRANCHE-COMTE'],
 ['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 numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap

fig = plt.figure(figsize=(20,10))
#Custom adjust of the subplots
#plt.subplots_adjust(left=0.05,right=0.95,top=0.90,bottom=0.05,wspace=0.15,hspace=0.05)
ax = plt.subplot(111)
#Let's create a basemap of Europe
x1 = -5.0
x2 = 12.
y1 = 40.
y2 = 54.

m = Basemap(resolution='i',projection='merc', llcrnrlat=y1,urcrnrlat=y2,llcrnrlon=x1,urcrnrlon=x2,lat_ts=(x1+x2)/2)
m.drawcountries(linewidth=0.5)
m.drawcoastlines(linewidth=0.5)
if False:
    # provoque l'erreur
    # ValueError: All values in the dash list must be positive
    m.drawparallels(np.arange(y1,y2,2.),labels=[1,0,0,0],color='black',
                    dashes=[1,0],labelstyle='+/-',linewidth=0.2) # draw parallels
    m.drawmeridians(np.arange(x1,x2,2.),labels=[0,0,0,1],color='black',
                    dashes=[1,0],labelstyle='+/-',linewidth=0.2) # draw meridians

from matplotlib.collections import LineCollection
from matplotlib import cm
import shapefile

shp = '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'
r = shapefile.Reader(shp)
shapes = r.shapes()
records = r.records()

for record, shape in zip(records,shapes):
    # les coordonnées sont en Lambert 93
    geo_points = [lambert932WGPS(x,y) for x, y in shape.points]
    lons = [_[0] for _ in geo_points]
    lats = [_[1] for _ in geo_points]
    data = np.array(m(lons, lats)).T

    if len(shape.parts) == 1:
        segs = [data,]
    else:
        segs = []
        for i in range(1,len(shape.parts)):
            index = shape.parts[i-1]
            index2 = shape.parts[i]
            segs.append(data[index:index2])
        segs.append(data[index2:])

    lines = LineCollection(segs,antialiaseds=(1,))
    # pour changer les couleurs c'est ici, il faudra utiliser le champ records
    # pour les changer en fonction du nom du départements
    lines.set_facecolors(cm.jet(np.random.rand(1)))
    lines.set_edgecolors('k')
    lines.set_linewidth(0.1)
    ax.add_collection(lines)
../_images/seance6_graphes_enonce_62_0.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)
<seaborn.axisgrid.PairGrid at 0x238dddb0c88>
../_images/seance6_graphes_enonce_66_1.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)
<seaborn.matrix.ClusterGrid at 0x238dd505198>
../_images/seance6_graphes_enonce_68_1.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)

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)

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)

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)

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.0647766052407.5502346130.2710274414: 0.5240196427157.535887682276.2185123130.296824472: 0.5688605969202.814717488215.083193110.2435905068: 0.5105939634109.378705836294.5228326860.2448575083: 0.5089349964111.602541096296.7846372990.1921446979: 0.35686400619.0812577912504.1153846150.2598471487: 0.5344739544137.912289243261.9652991510.289346381: 0.5188984103189.689206463283.2007048980.3436094453: 0.6469461912284.931488229108.6227527330.2279310664: 0.42630168781.8933277612409.4453475170.3040610498: 0.5625124908215.51632573223.7380823940.2943817999: 0.544246979198.527350634248.6409399280.2450954861: 0.4717058377112.02023854347.5421767490.2933821918: 0.5311928218196.772843048266.4387340160.3084466394: 0.5180154747223.21389257284.4044828370.3279448484: 0.5882997589257.437059576188.5802028320.2846826723: 0.5156611402181.503486423287.6143385440.2676430912: 0.5404444289151.595691645253.825265670.4296742368: 0.6486280588435.99201593106.3297258390.2778590727: 0.4845220526169.526735604330.068788760.3302260386: 0.5919094547261.440994112183.6588110790.3401543408: 0.6101681388278.867104749158.7652622760.3208868145: 0.591355662245.048830778184.4138416260.2628306315: 0.4808757424143.148884596335.0401000570.2504986939: 0.4918420525121.503924083320.0888376560.2465475746: 0.4754584072114.568937599342.4259936360.2570867555: 0.4652706844133.067259659356.3157445660.336956677: 0.5886362423273.254579838188.1214476290.2410806983: 0.4880095439104.973501501325.3140082490.3282833016: 0.5877897563258.031111073189.2755308340.31861502: 0.5664088131241.06138749218.4259094060.3175492669: 0.5660996845239.190782667218.8473694620.2669087013: 0.5131409025150.306693948291.0503835480.3176627647: 0.5571454684239.38999342231.0553805190.2987088695: 0.5566048435206.122203424231.7924583170.2810099775: 0.512271165175.057189259292.2361673470.2784438552: 0.521245461170.553143116280.0007798640.2460202605: 0.494249103113.643398369316.8071099620.3275273756: 0.5698881114256.704313293213.6822990410.2501102377: 0.4757464378120.822107544342.0332979870.2645875715: 0.5050122963146.232657548302.1327733990.2636337949: 0.5137946367144.558593159290.1590945410.3175662576: 0.5635370249239.220604664222.3412517130.2545099636: 0.459727615128.544486403363.8730617710.3451301832: 0.6188924192287.600680498146.8707413530.2691317444: 0.5135409093154.208569084290.5050216920.251549814: 0.4995315135123.348845382309.6051701750.2710428218: 0.4885272896157.562883214324.6081235310.2703860137: 0.4990071182156.410056794310.3201209640.2405223124: 0.4468609643103.993425119381.4152129610.237487769: 0.455749636198.6672083528369.2965639050.2586588102: 0.4692814875135.826522949350.8474904450.279223294: 0.5306282164171.921210602267.2085063780.2339890088: 0.461998146792.5262006096360.7774612720.2828813552: 0.5173251854178.341822852285.3456103170.2452890545: 0.4650352796112.359989006356.6366910310.326434194: 0.5881212704254.785565911188.8235507540.2796814818: 0.5287937639172.725419712269.7095646790.2490406428: 0.4733560214118.944759549345.2923471010.2426361408: 0.4711294378107.703606909348.3280296370.2943817208: 0.5618435736198.527211811224.6500715990.3308864131: 0.6046696171262.600080458166.2618440520.2993037086: 0.571203481207.166262204211.8889487220.3315941508: 0.6246555037263.842298464139.0134596330.2596311801: 0.5058602498137.533222113300.9766893870.1957599175: 0.36559510325.4266747623492.2115699750.1888947266: 0.3666556213.3769230769490.7656809130.2690870038: 0.4797769249154.130040513336.5382072540.2638327783: 0.4963742183144.907847975313.9097675380.2869166877: 0.5185616496185.424619972283.6598382040.2812678242: 0.5266959464175.509760602272.5696898830.2363860724: 0.470667203796.7335156464348.9582310.2051714488: 0.398973035741.9457513532446.7047201150.3482559745: 0.5560344101293.087054961232.5701766460.2940001524: 0.5494242986197.857484795241.5822791190.2764595121: 0.4925310938167.070233294319.1494116690.2731564701: 0.4570477056161.272749157367.5268002520.3333603951: 0.5730718101266.942402335209.3417037860.2837086077: 0.5440857882179.793812586248.8607044410.3068977754: 0.5555442098220.495333474233.2385064770.2763020762: 0.5125240293166.793902495291.8914169130.1964659664: 0.373608318326.6659285107481.2865020010.2233315968: 0.435665582173.8203598935396.6787879340.2481172555: 0.4440166679117.324034333385.2930734860.3198787089: 0.5716431017243.279408577211.2895781140.3590408325: 0.6399392027312.016588403118.1759498640.2468086779: 0.4906111046115.027225076321.7670889870.2399627718: 0.4688413485103.011321902351.4475677370.2600536551: 0.5052332051138.274748241301.8315903410.3039023794: 0.5343166443215.237828191262.1797727510.3015503263: 0.4948467982211.10951539315.9922235930.3868349831: 0.6531908835360.800754096100.1088558670.3293363765: 0.5647584825259.879463266220.6759393170.3241095145: 0.5390821527250.705299046255.6825676520.2831581923: 0.4694964979178.827726042350.5543493450.5699612149: 0.7193677038682.2230769239.884615384620.5198195119: 0.6843316045594.21458948557.65217867130.4261466909: 0.6204747063429.80048346144.7134806470.5328673249: 0.7148632408617.11605119516.02591612970.3375142531: 0.6530612245274.233234918100.2856305490.2677704469: 0.4057100716151.819225877437.519571230.3655059334: 0.4905494278323.36410375321.8511780610.4827532386: 0.5606441129529.156036619226.2853939610.324318908: 0.4694047691251.072825659350.6794107110.2491065327: 0.3697442124119.060409158486.5547518090.2228659974: 0.423966623573.003141981412.6289294680.2599373534: 0.455932439138.070615972369.047333835H