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.
Au passage, il montre comment visualiser l'index hiérarchique d'un DataFrame avec la librairie ETE Toolkit.
import pandas as pnd
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 à :
df = pnd.read_excel("FranceTHD_Open_Data_Observatoire_Juin2015.xlsx",\
sheetname="Communes",\
header=1)
df.head()
new_index = list(df.columns.values[0:3])
df.set_index(new_index, inplace=True)
df.head()
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 = ["Technologie", "Débit"]
df.head()
from ete3 import Tree, TreeStyle, TextFace, add_face_to_node
def make_tree(multi_index):
# textstyle spécifique
def my_layout(node):
tf = TextFace(node.name, tight_text=True, ftype='Calibri', fsize=12)
add_face_to_node(tf, node, column=0, position="branch-right")
ts = TreeStyle()
ts.show_leaf_name = False
ts.show_scale = False
ts.branch_vertical_margin = 3
ts.layout_fn = my_layout
# construction de l'arbre
t = Tree()
for branch in zip(*multi_index.labels): # on balaie chaque branche (de la racine jusqu'aux feuilles)
current_node = t
for i, j in enumerate(branch): # on balaie chaque branche de la racine jusqu'aux feuilles
# récupération du nom du noeud
node_name = multi_index.levels[i][int(j)]
nodes = current_node.search_nodes(name=node_name)
if nodes: # le noeud existe déjà et devient le noeud courrant
current_node = nodes[0]
else: # le noeud n'existe pas : il est ajouté au noeud courrant et devient le noeud courrant
child = Tree(node_name+';', format=8)
current_node.add_child(child=child,dist=1)
current_node = child
return t, ts
t, ts = make_tree(df.columns)
t.render("%%inline", tree_style=ts)
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.
# 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()
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.
df2 = pnd.read_csv("couverture-2g-3g-4g-en-france-par-operateur-juillet-2015.csv",\
sep=";")
df2.head()
new_index = list(df2.columns.values[0:6]) + [df2.columns.values[-1]]
df2.set_index(new_index, inplace=True)
df2.head()
df2.columns = [["Population"] * 15 + ["Surfacique"] * 15,\
(["4G"] * 5 + ["3G"] * 5 + ["2G"] * 5) * 2,\
["Orange", "Bouygues", "SFR", "Free", "Any"] * 6]
df2.columns.names = ["Couverture", "Technologie", "Opérateur"]
df2.head()
t, ts = make_tree(df2.columns)
t.render("%%inline", tree_style=ts)
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.
# noms des colonnes hiérarchiques
current_columns = df2.columns.names
# noms des colonnes de la table finale
new_columns = df2.index.names + current_columns + ["Value"]
# empilement des colonnes hiérarchiques vers l'index puis annulation de l'index
df2 = df2.stack(current_columns).reset_index()
df2.columns = new_columns
df2.head()
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.