Elections et cartes électorales - énoncé

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

D’après wikipédia, le Gerrymandering est un terme politique nord-américain pour désigner le découpage des circonscriptions électorales ayant pour objectif de donner l’avantage à un parti, un candidat, ou un groupe donné. Et c’est ce que nous allons faire dans cette séance. C’est un problème tout-à-fait d’actualité : Primaire de la droite : 10 228 bureaux de vote stratégiquement répartis.

%matplotlib inline
from jyquickhelper import add_notebook_menu
add_notebook_menu()
run previous cell, wait for 2 seconds

Données

Les données sont de plusieurs types et sont regroupées en trois fichiers :

Il est conseillé de télécharger directement ces données. Les paragraphes suivants expliquent comment ceux-ci ont été récupérés ou construit. Il n’est pas immédiat d’obtenir la localisation des bureaux de vote. Celle-ci n’est d’ailleurs pas complète.

Résultats des élections législatives

from actuariat_python.data import elections_legislatives_bureau_vote
tour = elections_legislatives_bureau_vote()
tour["T2"].sort_values(["Code département", "N° de circonscription Lg"]).head()
N° tour Code département Code de la commune Nom de la commune N° de circonscription Lg N° de canton N° de bureau de vote Inscrits Votants Exprimés N° de dépôt du candidat Nom du candidat Prénom du candidat Code nuance du candidat Nombre de voix du candidat
3858 2 01 16 Arbigny 1 26 0001 309 146 144 32 BRETON Xavier UMP 87
3859 2 01 16 Arbigny 1 26 0001 309 146 144 33 DEBAT Jean-François SOC 57
3871 2 01 24 Attignat 1 21 0001 746 425 411 32 BRETON Xavier UMP 233
3872 2 01 24 Attignat 1 21 0001 746 425 411 33 DEBAT Jean-François SOC 178
3873 2 01 24 Attignat 1 21 0002 1311 801 792 32 BRETON Xavier UMP 446

Géolocalisation des circonscription

from actuariat_python.data import elections_legislatives_circonscription_geo
geo = elections_legislatives_circonscription_geo()
geo.sort_values(["department", "code_circonscription"]).head()
code_circonscription department numero communes kml_shape simple_form
11 01001 01 1 01053-01072-01106-01150-01177-01184-01195-0124... <Polygon><outerBoundaryIs><LinearRing><coordin... False
12 01002 01 2 01008-01047-01099-01202-01213-01224-01366-0138... <Polygon><outerBoundaryIs><LinearRing><coordin... True
13 01003 01 3 01033-01044-01081-01091-01174-01189-01215-0125... <Polygon><outerBoundaryIs><LinearRing><coordin... True
14 01004 01 4 01023-01025-01026-01144-01159-01231-01320-0133... <Polygon><outerBoundaryIs><LinearRing><coordin... False
15 01005 01 5 01002-01004-01007-01041-01089-01149-01345-0137... <Polygon><outerBoundaryIs><LinearRing><coordin... True
c = list(geo.sort_values(["department", "code_circonscription"])["communes"])[0].split("-")
c.sort()
c[:5]
['01016', '01024', '01029', '01038', '01040']
list(geo.sort_values(["department", "code_circonscription"])["kml_shape"])[:1]
['<Polygon><outerBoundaryIs><LinearRing><coordinates>5.294455999999968,46.193934 5.279780999999957,46.201967 5.2820520000000215,46.211633 5.258239693115229,46.21151582325097 5.2581992646485105,46.2083773977195 5.246123010742167,46.20489445115752 5.245254158935495,46.20280868033653 5.240432576904368,46.199348983660926 5.23716710070812,46.19529946757567 5.229953412841837,46.195408656674 5.226543291137659,46.20120799400516 5.214944595275824,46.20383315680887 5.210212354492228,46.20764618417071 5.205343111083948,46.20376238484074 5.206997000000001,46.191323 5.19510600000001,46.18786 5.187468999999965,46.190827 5.170859999999948,46.203126 5.162395999999944,46.199387 5.15610700000002,46.189904 5.142047000000048,46.192223 5.123888999999963,46.207671 5.111119000000031,46.203891 5.097426000000041,46.206108 5.089811999999938,46.195268 5.068122000000017,46.208969 5.058287999999948,46.22074 5.05758000000003,46.224014 5.065923999999995,46.231556 5.093984999999975,46.232529 5.095609999999965,46.235634 5.0915129999999635,46.250725 5.067033000000038,46.253046 5.047692999999981,46.250829 5.034760000000006,46.25606 5.027125000000069,46.264835 5.022599000000014,46.270653 5.025237999999945,46.27702 5.0366390000000365,46.286329 5.03779499999996,46.296424 5.011349999999993,46.304235 5.008049000000028,46.313805 5.025981999999999,46.319235 5.0349670000000515,46.332568 5.018135000000029,46.347235 5.004950000000008,46.356205 4.98293000000001,46.359589 4.976036000000022,46.352773 4.958721999999966,46.358602 4.947116999999935,46.369538 4.932804000000033,46.367719 4.930385999999999,46.372948 4.8962810000000445,46.384617 4.897916000000009,46.398713 4.888207999999963,46.402982 4.888682000000017,46.417448 4.891531999999984,46.427525 4.891796999999997,46.438555 4.892886999999973,46.442572 4.894444000000021,46.445374 4.914447999999993,46.462163 4.914530000000013,46.480113 4.914522000000034,46.481374 4.91528500000004,46.488234 4.926993000000039,46.499251 4.92961600000001,46.50408 4.933591999999976,46.512453 4.935599000000025,46.514229 4.947349000000031,46.513723 4.946786999999972,46.507113 4.953710999999998,46.504112 4.962766999999985,46.506749 4.97047299999997,46.51421 4.980216000000041,46.515203 5.00636899999995,46.509703 5.010593999999969,46.51107 5.01423299999999,46.50045 5.05475100000001,46.484281 5.07144900000003,46.485817 5.09522800000002,46.497508 5.112967000000026,46.493137 5.134029999999939,46.501447 5.136367000000064,46.507808 5.14104199999997,46.508585 5.164164000000028,46.504745 5.165839000000005,46.517675 5.177177000000029,46.511434 5.201221000000032,46.507821 5.203565000000026,46.504803 5.200342999999975,46.502568 5.2094809999999825,46.492105 5.206684999999993,46.486177 5.213031000000001,46.481541 5.215063999999984,46.468359 5.224697999999989,46.468352 5.236259000000018,46.457785 5.248004000000037,46.459374 5.25627099999997,46.451879 5.273677000000021,46.448588 5.310561000000007,46.446775 5.32172700000001,46.428955 5.309671999999978,46.423615 5.307822999999985,46.417157 5.312332999999967,46.416021 5.298806000000013,46.415528 5.299958999999944,46.412289 5.309146000000055,46.410257 5.334092000000055,46.399723 5.3481160000000045,46.399417 5.367105000000038,46.388692 5.377881000000002,46.382324 5.365074999999933,46.373077 5.363472000000002,46.37002 5.375389000000041,46.364844 5.375761000000011,46.358431 5.373462000000018,46.352236 5.383367000000021,46.344877 5.400995999999964,46.3393 5.401686000000041,46.332799 5.4040410000000065,46.315668 5.404053999999974,46.314767 5.410239999999931,46.309105 5.416931999999974,46.342873 5.423534000000018,46.347732 5.437145999999984,46.315127 5.454476,46.317998 5.465180000000032,46.323542 5.475304999999935,46.315385 5.466531000000032,46.293242 5.459000999999944,46.290531 5.457672000000002,46.276848 5.471308000000022,46.26721 5.475362000000018,46.265221 5.499594999999999,46.268205 5.502609000000007,46.270211 5.511369999999943,46.26436 5.51186400000006,46.257853 5.50287800000001,46.245411 5.494106999999985,46.242144 5.46908099999996,46.212365 5.464535999999953,46.210869 5.460665000000063,46.213097 5.454173999999966,46.216805 5.450403000000051,46.215184 5.451294999999959,46.208723 5.46410000000003,46.204347 5.466176000000019,46.191076 5.46054300000003,46.18609 5.454643000000033,46.181506 5.461671000000024,46.177417 5.446460000000002,46.152159 5.430090999999948,46.139357 5.43449899999996,46.125677 5.431788999999981,46.122699 5.421692000000007,46.1226 5.4127750000000106,46.124015 5.4093820000000505,46.118726 5.405267999999978,46.118273 5.39823899999999,46.098224 5.401965000000018,46.08837 5.399834999999939,46.085253 5.386187000000064,46.081649 5.383172000000059,46.075109 5.376326000000063,46.059 5.378545000000031,46.053161 5.374886999999944,46.048147 5.367076999999995,46.044365 5.352794000000017,46.04495 5.351631999999995,46.035335 5.339034999999967,46.039632 5.334828000000016,46.038052 5.324753999999984,46.04179 5.3118409999999585,46.025424 5.310352999999964,46.011554 5.305977999999982,46.001878 5.299062000000049,45.99691 5.29071399999998,45.993548 5.286965000000009,45.987002 5.275816999999961,45.998064 5.27370099999996,46.015052 5.25696199999993,46.021486 5.263284999999996,46.034044 5.261357999999973,46.03719 5.2586579999999685,46.043559 5.254462999999987,46.048534 5.2451469999999745,46.041863 5.2245040000000245,46.034586 5.210403000000042,46.035808 5.2019599999999855,46.050404 5.187967999999955,46.052846 5.176518999999985,46.064011 5.178200000000061,46.081266 5.1829139999999825,46.080995 5.1864799999999605,46.087331 5.194266999999968,46.09137 5.235487000000035,46.100474 5.239908000000014,46.112963 5.235547999999994,46.129318 5.238933999999972,46.135783 5.2344419999999445,46.145429 5.246138999999971,46.14909 5.248986999999943,46.154682 5.2586180000000695,46.156266 5.277324000000021,46.151893 5.29046500000004,46.154917 5.307536000000027,46.153346 5.312217000000032,46.16107 5.3077429999999595,46.166278 5.310639000000037,46.168562 5.304237000000057,46.172322 5.300876000000017,46.18093 5.294455999999968,46.193934</coordinates></LinearRing></outerBoundaryIs></Polygon>']

Géolocation des bureaux de vote

Ces données sont importantes afin de pouvoir associer chaque bureau à une circonscription. C’est la donnée la plus compliquée à obtenir car elle nécessite de combiner plusieurs techniques et sources de données pour obtenir une table propre. Le fichier final peut être obtenu comme suit :

from actuariat_python.data import elections_vote_places_geo
bureau_geo = elections_vote_places_geo()
bureau_geo.head()
address city n place zip full_address latitude longitude geo_address
0 cours verdun bourg 1 salle des fêtes 1000 cours verdun 01000 bourg 46.206605 5.228364 Cours de Verdun, Le Peloux, Les Vennes, Bourg-...
1 cours verdun bourg 2 salle des fêtes 1000 cours verdun 01000 bourg 46.206605 5.228364 Cours de Verdun, Le Peloux, Les Vennes, Bourg-...
2 rue antoine de saint exupéry bourg-en-bresse 3 groupe scolaire saint-exupéry - salle de jeux,... 1000 rue antoine de saint exupéry 01000 bourg-en-br... 46.210030 5.233330 Rue Antoine de Saint-Exupéry, Bourg-en-Bresse,...
3 11 avenue de l’égalité bourg-en-bresse 4 charles perrault - école primaire 1000 11 avenue de l’égalité 01000 bourg-en-bresse 46.214848 5.231941 11, Avenue de l'Égalité, Saint-Georges, La Gla...
4 11 avenue de l’égalité bourg-en-bresse 5 charles perrault - école primaire 1000 11 avenue de l’égalité 01000 bourg-en-bresse 46.214848 5.231941 11, Avenue de l'Égalité, Saint-Georges, La Gla...

Ce qui suit explique la façon dont j’ai constuit cette table.

Les bureaux sont assez stables d’une élection à l’autre et cela ne devrait pas trop poser de problèmes si on mélange les données. En revanche, ces données sont assez difficiles à obtenir. open.data.fr propose ces données mais il faut récupérer chaque ville ou chaque région séparément sans garantie de réussir à couvrir tout le territoire. Le site NosDonnes.fr recense bien toutes ces informations mais il n’est pas possible - au moment où j’écris ces lignes - de récupérer ces données sous la forme d’un fichier plat. De plus, certaines régions ne sont disponibles que sous forme de scan d’impressions papier. C’est en lisant l’article Comment redécoupe-t-on la carte électorale? que je suis tombé finalement sur la base constituée pour rédiger l’article Etude sur le redécoupage électoral : évaluer le poids politique de la réforme. Les données sont accessibles sur RegardsCitoyens.fr et en cherchant bien, on trouve le répertoire redécoupage et le fichier 2014041401_resultats.csv.zip. Cependant, la géolocalisation des bureaux n’est pas souvent renseignée. On peut se contenter des fichiers obtenus pour quelques zones seulement : Paris, Gironde, Montpellier, Marseille, Saint-Malo, Nogent-Sur-Marne, Haut-de-Seine, Toulouse, Grand-Poitiers, Calvados. Cette approche risque d’être fastidieuse dans la mesure où les formats pour chaque ville ont de grande chance d’être différent.

La meilleure option est peut-être de scrapper le site bureaudevote.fr - qui fonctionne bien quand il n’est pas en maintenance - en espérant que les numéros des bureaux de votes correspondent. Le site propose seulement les adresses. Il faudra utiliser une API de geocoding. Quelque soit la source, on supposera alors que tous les bureaux de vote associés au même emplacement feront nécessairement partie de la même circonscription. Bref, l’open data passe aussi par une certaine standardisation !

Certains départements ne sont pas renseignés, la Drôme par exemple. Il faut aller sur d’autres sites comme linternaute.com, accéder à la carte, mais il faudra un peu plus de temps pour récupérer toutes ces informations. Le code proposé ci-dessus récupère les coordonnées des bureaux de vote. Cela prend plusieurs heures et il faut relancer le processus quand il s’arrête pour une erreur de réseau. Il est conseillé de télécharger le résultat. Le géocodeur d’OpenStreetMap n’est pas de très bonne qualité sur la France. Il ne retourne rien dans plus d’un tiers des cas. On peut compléter avec l’API de Bing Maps.

from actuariat_python.data import elections_vote_place_address
bureau = elections_vote_place_address(hide_warning=True)
bureau.head()
address city n place zip
0 cours verdun bourg 1 salle des fêtes 01000
1 cours verdun bourg 2 salle des fêtes 01000
2 rue antoine de saint exupéry bourg-en-bresse 3 groupe scolaire saint-exupéry - salle de jeux,... 01000
3 11 avenue de l’égalité bourg-en-bresse 4 charles perrault - école primaire 01000
4 11 avenue de l’égalité bourg-en-bresse 5 charles perrault - école primaire 01000

On récupère une clé pour utiliser l’API de Bing Maps avec le module keyring. Pour stocker son mot de passe sur la machine, il suffit d’écrire :

import keyring, os
bing_key = keyring.get_password("bing", os.environ["COMPUTERNAME"])
coders = ["Nominatim"]
if bing_key:
    # si la clé a été trouvée
    coders.append(("bing", bing_key))
len(coders)
2
import os
if not os.path.exists("bureauxvotegeo.zip"):
    from actuariat_python.data import geocode
    from pyquickhelper.loghelper import fLOG
    fLOG(OutputPrint=True)
    bureau_geo = geocode(bureau, fLOG=fLOG, index=False, encoding="utf-8",
                         exc=False, save_every="bureau.dump.txt", sep="\t", every=100,
                         coders=coders)
else:
    print("Les données ont déjà été geocodées.")
Les données ont déjà été geocodées.

On regarde les valeurs manquantes.

import missingno
missingno.matrix(bureau_geo, figsize=(12, 6))
../_images/election_carte_electorale_22_0.png

On pourra finalement récupérer la base des géocodes comme ceci :

from actuariat_python.data import elections_vote_places_geo
places = elections_vote_places_geo()
places.head()
address city n place zip full_address latitude longitude geo_address
0 cours verdun bourg 1 salle des fêtes 1000 cours verdun 01000 bourg 46.206605 5.228364 Cours de Verdun, Le Peloux, Les Vennes, Bourg-...
1 cours verdun bourg 2 salle des fêtes 1000 cours verdun 01000 bourg 46.206605 5.228364 Cours de Verdun, Le Peloux, Les Vennes, Bourg-...
2 rue antoine de saint exupéry bourg-en-bresse 3 groupe scolaire saint-exupéry - salle de jeux,... 1000 rue antoine de saint exupéry 01000 bourg-en-br... 46.210030 5.233330 Rue Antoine de Saint-Exupéry, Bourg-en-Bresse,...
3 11 avenue de l’égalité bourg-en-bresse 4 charles perrault - école primaire 1000 11 avenue de l’égalité 01000 bourg-en-bresse 46.214848 5.231941 11, Avenue de l'Égalité, Saint-Georges, La Gla...
4 11 avenue de l’égalité bourg-en-bresse 5 charles perrault - école primaire 1000 11 avenue de l’égalité 01000 bourg-en-bresse 46.214848 5.231941 11, Avenue de l'Égalité, Saint-Georges, La Gla...

Géolocalisation des villes

from actuariat_python.data import elections_vote_places_geo
bureau_geo = elections_vote_places_geo()
villes_geo = bureau_geo[["city", "zip", "n"]].groupby(["city", "zip"], as_index=False).count()
villes_geo.head()
city zip n
0 abbeville 80100 17
1 ableiges 95450 2
2 ablis 78660 2
3 ablon 94480 4
4 achères 78260 12
from actuariat_python.data import villes_geo
villes_geo = villes_geo(as_df=True)
villes_geo.head()
city zip n full_address latitude longitude geo_address
0 abbeville 80100 17 80100 abbeville France 30.206659 -92.008957 Nan Dr, Lafayette, LA 70503, United States
1 ableiges 95450 2 95450 ableiges France 49.090165 1.981233 Ableiges, Pontoise, Val-d'Oise, Île-de-France,...
2 ablis 78660 2 78660 ablis France 48.517153 1.836876 Ablis, Rambouillet, Yvelines, Île-de-France, 7...
3 ablon 94480 4 94480 ablon France 48.723640 2.414800 Rue d'Ablon, Ablon-sur-Seine, IdF 94480, France
4 achères 78260 12 78260 achères France 48.960266 2.070165 Achères, Saint-Germain-en-Laye, Yvelines, Île-...
import keyring, os
bing_key = keyring.get_password("bing", os.environ["COMPUTERNAME"])
coders = []
if bing_key:
    # si la clé a été trouvée
    coders.append(("bing", bing_key))
len(coders)
1
import os
geocode = True
if geocode:
    from actuariat_python.data import geocode
    from pyquickhelper.loghelper import fLOG
    fLOG(OutputPrint=True)
    villes_geo = geocode(villes_geo, fLOG=fLOG, index=False, encoding="utf-8",
                         exc=False, save_every="villes.dump.txt", sep="\t", every=100,
                         coders=coders, country="France")
---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
<ipython-input-20-1b7d21f199be> in <module>()
      7     villes_geo = geocode(villes_geo, fLOG=fLOG, index=False, encoding="utf-8",
      8                          exc=False, save_every="villes.dump.txt", sep="\t", every=100,
----> 9                          coders=coders, country="France")

C:\github\actuariat_python\src\actuariat_python\data\geocoding.py in geocode(df, col_city, col_place, col_zip, col_address, col_latitude, col_longitude, col_full, col_geo, save_every, every, exc, fLOG, coders, country, **options)
     54     unless there was no answer on the first call.
     55     """
---> 56     from geopy.geocoders import Nominatim, Bing
     57
     58     def get_coder(d):

ImportError: No module named 'geopy'
villes_geo.head()

Géolocation des bureaux de vote avec Cartélec

Le site cartelec recense beaucoup plus de bureaux de vote mais pour les élections 2007. Ils ne devraient pas avoir changé beaucoup.

from pyensae.datasource import download_data
shp_vote = download_data("base_cartelec_2007_2010.zip")
shp_vote
import shapefile
r = shapefile.Reader("fond0710.shp")
shapes = r.shapes()
records = r.records()
len(shapes), len(records)
{k[0]:v for k,v in zip(r.fields, records[0])}
shapes[0].points

Exercice 1

  • Etablir un plan d’action
  • Détailler la mise en oeuvre de ce plan à partir des données
  • Répartir les tâches sur plusieurs équipes

Exercice 2

Mettre en oeuvre le plan d’action.