Mise à plat de données opendata en Python

L’exploitation des données ouvertes est souvent tributaire du modèle des données publiées. Il arrive souvent que les données soient publiées dans un format spécifique difficilement exploitable car présentant des informations qui relèveraient d’une représentation hiérarchique mais qui par commodité sont livrées sans réelle organisation.

Cet article vise à montrer comment il est possible en utilisant la librairie Python/pandas de mettre à plat les données opendata sous la forme d’une table unique.

In [1]:
import pandas as pnd

Exemple n° 1 : France Très Haut Débit

Le premier exemple est tiré du site France Très Haut Débit. Il propose un fichier au format Excel avec par commune : les couvertures par technologie (DSL, câble et fibre) et par débit (éligible, 3, 8, 30 et 100 Mbits/s). Au total, le fichier présente 23 colonnes.

Le principe de mise à plat consiste à :

  • transférer les colonnes singulières vers l’index
  • hiérarchiser les colonnes relatives aux technologies et aux débits
  • renvoyer les colonnes hiérarchisées vers l’index
  • supprimer l’index

Chargement du fichier Excel

In [2]:
df = pnd.read_excel("FranceTHD_Open_Data_Observatoire_Juin2015.xlsx",\
                    sheetname="Communes",\
                    header=1)
df.head()
Out[2]:
Département Code INSEE Commune éligibles 3 Mbit/s et + 8 Mbit/s et + 30 Mbit/s et + 100 Mbit/s et + éligibles.1 3 Mbit/s et +.1 ... éligibles.2 3 Mbit/s et +.2 8 Mbit/s et +.2 30 Mbit/s et +.2 100 Mbit/s et +.2 éligibles.3 3 Mbit/s et +.3 8 Mbit/s et +.3 30 Mbit/s et +.3 100 Mbit/s et +.3
0 AIN 01001 L'Abergement-Clémenciat 1.000 0.448 0.052 0.000 0.000 1.000 0.448 ... 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
1 AIN 01002 L'Abergement-de-Varey 0.676 0.594 0.571 0.571 0.571 0.124 0.024 ... 0.0 0.0 0.0 0.0 0.0 0.571 0.571 0.571 0.571 0.571
2 AIN 01004 Ambérieu-en-Bugey 1.000 0.966 0.794 0.234 0.000 1.000 0.966 ... 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
3 AIN 01005 Ambérieux-en-Dombes 1.000 0.990 0.942 0.667 0.004 1.000 0.985 ... 0.0 0.0 0.0 0.0 0.0 0.004 0.004 0.004 0.004 0.004
4 AIN 01006 Ambléon 1.000 1.000 1.000 0.934 0.934 1.000 1.000 ... 0.0 0.0 0.0 0.0 0.0 0.934 0.934 0.934 0.934 0.934

5 rows × 23 columns

Transfert des 3 colonnes singulières vers l'index

In [3]:
new_index = list(df.columns.values[0:3])
df.set_index(new_index, inplace=True)
df.head()
Out[3]:
éligibles 3 Mbit/s et + 8 Mbit/s et + 30 Mbit/s et + 100 Mbit/s et + éligibles.1 3 Mbit/s et +.1 8 Mbit/s et +.1 30 Mbit/s et +.1 100 Mbit/s et +.1 éligibles.2 3 Mbit/s et +.2 8 Mbit/s et +.2 30 Mbit/s et +.2 100 Mbit/s et +.2 éligibles.3 3 Mbit/s et +.3 8 Mbit/s et +.3 30 Mbit/s et +.3 100 Mbit/s et +.3
Département Code INSEE Commune
AIN 01001 L'Abergement-Clémenciat 1.000 0.448 0.052 0.000 0.000 1.000 0.448 0.052 0.000 0 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
01002 L'Abergement-de-Varey 0.676 0.594 0.571 0.571 0.571 0.124 0.024 0.000 0.000 0 0.0 0.0 0.0 0.0 0.0 0.571 0.571 0.571 0.571 0.571
01004 Ambérieu-en-Bugey 1.000 0.966 0.794 0.234 0.000 1.000 0.966 0.794 0.234 0 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
01005 Ambérieux-en-Dombes 1.000 0.990 0.942 0.667 0.004 1.000 0.985 0.937 0.663 0 0.0 0.0 0.0 0.0 0.0 0.004 0.004 0.004 0.004 0.004
01006 Ambléon 1.000 1.000 1.000 0.934 0.934 1.000 1.000 1.000 0.000 0 0.0 0.0 0.0 0.0 0.0 0.934 0.934 0.934 0.934 0.934

Création d'une hiérarchie avec les colonnes répétées

In [4]:
df.columns = [["Tout"] * 5 + ["DSL"] * 5 + ["Câble"] * 5 + ["Fibre"] * 5,\
              ["1 Mbit", "3 Mbit", "8 Mbit", "30 Mbit", "100 Mbit"] * 4]
df.columns.names = ["Techno", "Débit"]
df.head()
Out[4]:
Techno Tout DSL Câble Fibre
Débit 1 Mbit 3 Mbit 8 Mbit 30 Mbit 100 Mbit 1 Mbit 3 Mbit 8 Mbit 30 Mbit 100 Mbit 1 Mbit 3 Mbit 8 Mbit 30 Mbit 100 Mbit 1 Mbit 3 Mbit 8 Mbit 30 Mbit 100 Mbit
Département Code INSEE Commune
AIN 01001 L'Abergement-Clémenciat 1.000 0.448 0.052 0.000 0.000 1.000 0.448 0.052 0.000 0 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
01002 L'Abergement-de-Varey 0.676 0.594 0.571 0.571 0.571 0.124 0.024 0.000 0.000 0 0.0 0.0 0.0 0.0 0.0 0.571 0.571 0.571 0.571 0.571
01004 Ambérieu-en-Bugey 1.000 0.966 0.794 0.234 0.000 1.000 0.966 0.794 0.234 0 0.0 0.0 0.0 0.0 0.0 0.000 0.000 0.000 0.000 0.000
01005 Ambérieux-en-Dombes 1.000 0.990 0.942 0.667 0.004 1.000 0.985 0.937 0.663 0 0.0 0.0 0.0 0.0 0.0 0.004 0.004 0.004 0.004 0.004
01006 Ambléon 1.000 1.000 1.000 0.934 0.934 1.000 1.000 1.000 0.000 0 0.0 0.0 0.0 0.0 0.0 0.934 0.934 0.934 0.934 0.934

Mise à plat des données

Après mise à plat, les données peuvent être représentées sous la forme d’une table à 6 colonnes seulement : département, code INSEE, commune, technologie, débit et valeur, bien plus facile à exploiter.

In [5]:
# noms des colonnes hiérarchiques
current_columns = df.columns.names
# noms des colonnes de la table finale
new_columns = df.index.names + current_columns + ["Value"]
# empilement des colonnes hiérarchiques vers l'index puis annulation de l'index
df = df.stack(current_columns).reset_index()
df.columns = new_columns
df.head()
Out[5]:
Département Code INSEE Commune Techno Débit Value
0 AIN 01001 L'Abergement-Clémenciat Câble 1 Mbit 0.0
1 AIN 01001 L'Abergement-Clémenciat Câble 100 Mbit 0.0
2 AIN 01001 L'Abergement-Clémenciat Câble 3 Mbit 0.0
3 AIN 01001 L'Abergement-Clémenciat Câble 30 Mbit 0.0
4 AIN 01001 L'Abergement-Clémenciat Câble 8 Mbit 0.0

Exemple n° 2 : France Couverture 2G/3G/4G

Le second exemple, toujours dans le domaine des télécoms, est tiré du site OpenDataSoft. Il propose un fichier CSV avec par commune : les couvertures en population et surfacique par opérateur (Orange, Bouygues, SFR et Free) et par technologie (2G, 3G et 4G). Au total, le fichier présente 37 colonnes.

Chargement du fichier CSV

In [6]:
df = pnd.read_csv("couverture-2g-3g-4g-en-france-par-operateur-juillet-2015.csv",\
                 sep=";")
df.head()
Out[6]:
CODE POSTAL CODE INSEE NOM COMMUNE CODE DEPARTEMENT SURFACE COMMUNE (km²) POPULATION COMMUNE Orange France Couverture population 4G Bouygues Telecom Couverture population 4G SFR Couverture population 4G Free Mobile Couverture population 4G ... Bouygues Telecom Couverture population 2G SFR Couverture population 2G Free Mobile Couverture population 2G Par au moins un opérateur Couverture population 2G Orange France Couverture surfacique 2G Bouygues Telecom Couverture surfacique 2G SFR Couverture surfacique 2G Free Mobile Couverture surfacique 2G Par au moins un opérateur Couverture surfacique 2G coordonnees
0 17510 17105 CHIVES 17 20.642005 355.32 0.00 0.0 0.0 0.0 ... 1.00 1.0 1.0 1.0 1.00 0.99 1.0 1.00 1.0 45.9508835378, -0.121918052323
1 17600 17112 LA CLISSE 17 4.896771 591.78 0.09 0.0 0.0 0.0 ... 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0 45.7320429784, -0.75493581331
2 17150 17116 CONSAC 17 8.915911 239.74 0.00 0.0 0.0 0.0 ... 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0 45.4182495786, -0.588917528415
3 17130 17118 CORIGNAC 17 10.850210 358.69 0.00 0.0 0.0 0.0 ... 0.63 1.0 1.0 1.0 0.99 0.85 1.0 0.99 1.0 45.2448604983, -0.39280322021
4 17330 17124 COURANT 17 15.586269 371.00 0.00 0.0 0.0 0.0 ... 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0 46.0375312829, -0.587834905531

5 rows × 37 columns

Transfert des 7 colonnes singulières vers l'index

In [7]:
new_index = list(df.columns.values[0:6]) + [df.columns.values[-1]]
df.set_index(new_index, inplace=True)
df.head()
Out[7]:
Orange France Couverture population 4G Bouygues Telecom Couverture population 4G SFR Couverture population 4G Free Mobile Couverture population 4G Par au moins un opérateur Couverture population 4G Orange France Couverture surfacique 4G Bouygues Telecom Couverture surfacique 4G SFR Couverture surfacique 4G Free Mobile Couverture surfacique 4G Par au moins un opérateur Couverture surfacique 4G ... Orange France Couverture population 2G Bouygues Telecom Couverture population 2G SFR Couverture population 2G Free Mobile Couverture population 2G Par au moins un opérateur Couverture population 2G Orange France Couverture surfacique 2G Bouygues Telecom Couverture surfacique 2G SFR Couverture surfacique 2G Free Mobile Couverture surfacique 2G Par au moins un opérateur Couverture surfacique 2G
CODE POSTAL CODE INSEE NOM COMMUNE CODE DEPARTEMENT SURFACE COMMUNE (km²) POPULATION COMMUNE coordonnees
17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 0.99 1.0 1.00 1.0
17600 17112 LA CLISSE 17 4.896771 591.78 45.7320429784, -0.75493581331 0.09 0.0 0.0 0.0 0.09 0.2 0.0 0.0 0.0 0.2 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0
17150 17116 CONSAC 17 8.915911 239.74 45.4182495786, -0.588917528415 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0
17130 17118 CORIGNAC 17 10.850210 358.69 45.2448604983, -0.39280322021 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 0.63 1.0 1.0 1.0 0.99 0.85 1.0 0.99 1.0
17330 17124 COURANT 17 15.586269 371.00 46.0375312829, -0.587834905531 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0

5 rows × 30 columns

Création d'une hiérarchie avec les colonnes répétées

In [8]:
df.columns = [["Population"] * 15 + ["Surfacique"] * 15,\
              (["4G"] * 5 + ["3G"] * 5 + ["2G"] * 5) * 2,\
              ["Orange", "Bouygues", "SFR", "Free", "Any"] * 6]
df.columns.names = ["Type", "Techno", "Opérateur"]
df.head()
Out[8]:
Type Population ... Surfacique
Techno 4G 3G ... 3G 2G
Opérateur Orange Bouygues SFR Free Any Orange Bouygues SFR Free Any ... Orange Bouygues SFR Free Any Orange Bouygues SFR Free Any
CODE POSTAL CODE INSEE NOM COMMUNE CODE DEPARTEMENT SURFACE COMMUNE (km²) POPULATION COMMUNE coordonnees
17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 0.99 1.0 1.00 1.0
17600 17112 LA CLISSE 17 4.896771 591.78 45.7320429784, -0.75493581331 0.09 0.0 0.0 0.0 0.09 0.2 0.0 0.0 0.0 0.2 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0
17150 17116 CONSAC 17 8.915911 239.74 45.4182495786, -0.588917528415 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0
17130 17118 CORIGNAC 17 10.850210 358.69 45.2448604983, -0.39280322021 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 0.63 1.0 1.0 1.0 0.99 0.85 1.0 0.99 1.0
17330 17124 COURANT 17 15.586269 371.00 46.0375312829, -0.587834905531 0.00 0.0 0.0 0.0 0.00 0.0 0.0 0.0 0.0 0.0 ... 1.0 1.00 1.0 1.0 1.0 1.00 1.00 1.0 1.00 1.0

5 rows × 30 columns

Mise à plat des données

A nouveau, après mise à plat, les données peuvent être représentées sous la forme d’une table à 11 colonnes seulement : codes postal et INSEE, commune, département, surface, population, coordonnées, type, technologie, opérateur et valeur.

In [9]:
# noms des colonnes hiérarchiques
current_columns = df.columns.names
# noms des colonnes de la table finale
new_columns = df.index.names + current_columns + ["Value"]
# empilement des colonnes hiérarchiques vers l'index puis annulation de l'index
df = df.stack(current_columns).reset_index()
df.columns = new_columns
df.head()
Out[9]:
CODE POSTAL CODE INSEE NOM COMMUNE CODE DEPARTEMENT SURFACE COMMUNE (km²) POPULATION COMMUNE coordonnees Type Techno Opérateur Value
0 17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 Population 2G Any 1.00
1 17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 Population 2G Bouygues 0.44
2 17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 Population 2G Free 0.18
3 17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 Population 2G Orange 0.97
4 17510 17105 CHIVES 17 20.642005 355.32 45.9508835378, -0.121918052323 Population 2G SFR 1.00

N.B. : la séparation de la latitude et de la longitude de la colonne « coordonnées » n’est pas traitée dans cet exemple.

Copyright © 2016 Yotta Conseil