title:      ★ De l&#39;OpenData au LinkedData : exemple de nosdonnees.fr
slug:       de-lopendata-au-linkeddata-exemple-de-nosdonneesfr
date:       2010-11-30 12:26:27
type:       post
vignette:   images/logos/kamehameha.png
contextual_title1:  Les outils manquants de l&#39;OpenData
contextual_url1:    20110328-les-outils-manquants-opendata
contextual_title2:  Retour sur l&#39;OpenData et nous, et nous, et nous ? 
contextual_url2:    20110322-retour-sur-lopendata-et-nous-et-nous-et-nous
contextual_title3:  Un projet Python : de l&#39;idée à la publication
contextual_url3:    20101203-un-projet-python-de-lidee-la-publication

Je suis en train d'écrire un framework permettant d'apporter du sens et du lien à des données plates (csv, xls, etc), l'objectif est de les rendre directement exploitables par les utilisateurs et surtout d'en faciliter la publication pour les détenteurs originaux. J'ai choisi d'en tester l'application sur des données répertoriées dans le tout nouveau [nosdonnees.fr](http://www.nosdonnees.fr/) dont je salue l'initiative citoyenne.


L'avantage d'avoir des données réelles est de pouvoir arriver à des analyses marrantes, je me suis donc concentré sur l'[Impôt de Solidarité sur la Fortune par commune 2009](http://www.nosdonnees.fr/package/isf2009) et sur le [Taux de Fiscalité Directe Locale par région 2001-2009](http://www.nosdonnees.fr/package/taux_fiscalite_2001_2009). La seule manipulation a été de convertir les fichiers xls en csv mais je n'ai pas essayé de nettoyer les données en amont. Pouvoir le faire est un confort dont il ne faut pas se priver mais ce n'est pas forcément possible et maintenable, même si des outils comme [Google Refine](http://code.google.com/p/google-refine/wiki/GettingStarted) (l'ancien Freebase Gridworks) rendent la chose beaucoup plus aisée (tiens [une extension RDF](http://lab.linkeddata.deri.ie/2010/grefine-rdf-extension/)).


## Modélisation ##

Analysons le premier jeu de données qui nous donne l'ISF pour l'année 2009 en fonction des communes, regroupées par région. L'idée du framework est de pouvoir facilement gérer des objet à partir de ces lignes, ce qui est brillamment décrit dans le dernier chapitre du livre ProPython, on arrive à la définition suivante :

    class CommuneResource(Resource):
        region = StringTriple()
        departement = StringTriple()
        code_commune = StringTriple()
        commune = StringTriple()
        redevables = IntegerTriple()
        patrimoine_moyen = IntegerTriple()
        impot_moyen = IntegerTriple()
    

À partir de là, je veux pouvoir typer certaines des colonnes de mes données et j'ai connaissance du [vocabulaire défini par l'INSEE](http://rdf.insee.fr/geo/) pour justement décrire des régions et département. Une rapide lecture du [fichier RDF des régions](http://rdf.insee.fr/geo/regions-2010.rdf) m'informe qu'il existe une classe pour les régions et départements ainsi que pour les communes et leur code. Je déclare donc ces références dans la définition de ma classe (au passage je lui attribue une URI et un type général) :

    INSEE_GEO = Namespace('http://rdf.insee.fr/geo/')
    
    class CommuneResource(Resource):
        region = StringTriple(rdf_type=INSEE_GEO.Region)
        departement = StringTriple(rdf_type=INSEE_GEO.Departement)
        code_commune = StringTriple(rdf_type=INSEE_GEO.code_commune)
        commune = StringTriple(rdf_type=INSEE_GEO.Commune)
        redevables = IntegerTriple()
        patrimoine_moyen = IntegerTriple()
        impot_moyen = IntegerTriple()
        
        class Options:
            rdf_type = RDFS.Resource
            resource_uri = 'http://example.org/communeresource/%s/'
    
L'essentiel du travail est fait, je charge tout ça dans un graphe RDF et je peux commencer à jouer avec :

    isf_data_filepath = 'data/isf/ISF2009-Tableau 1.csv'
    isf_data_file = open(isf_data_filepath, 'r')
    g = ConjunctiveGraph()
    for line in CommuneResource.loader(isf_data_file, **{'delimiter': ';'}):
        line.save(store=g)
        
    line.commune # -> retourne 'CHAMBERY'

À partir de là, on peut par exemple calculer la moyenne de l'ISF moyen par individu redevable de cet impôt et on arrive à une surprise (en tout cas pour moi) :

    regions = defaultdict(list)
    for commune_uri in g.subjects(RDF.type, RDFS.Resource):
        commune = CommuneResource().objects.get(str(commune_uri), store=g)
        regions[commune.region].append(commune.impot_moyen)

    for region, impot_list in regions.items():
        regions[region] = sum(impot_list)/len(impot_list)
    
    regions = regions.items()
    regions.sort(key=lambda a: a[1], reverse=True)
    print regions[:4]
    # -> retourne
    [('GUYANE', 15044), ('MARTINIQUE', 9579), ('GUADELOUPE', 7379), ('CORSE', 6811)])

Il semblerait que les riches les plus riches soient en moyenne dans les DOM-TOM (non, la Corse n'en est pas (encore) un ;-)).


## Relations ##

Bon jusque là rien de bien phénoménal, passons à la partie intéressante, la possibilité de lier les données avec d'autres sources, par exemple la taxe foncière pour analyser si les plus riches habitent là où la taxe foncière est la plus basse (quelle drôle d'idée...). On prend le second jeu de données, converti également en csv et on crée une nouvelle ressource :

    class RegionResource(Resource):
        code_region = StringTriple(rdf_type=INSEE_GEO.code_region)
        region = StringTriple(rdf_type=INSEE_GEO.Region)
        annee2001 = DecimalTriple()
        annee2002 = DecimalTriple()
        annee2003 = DecimalTriple()
        annee2004 = DecimalTriple()
        annee2005 = DecimalTriple()
        annee2006 = DecimalTriple()
        annee2007 = DecimalTriple()
        annee2008 = DecimalTriple()
        annee2009 = DecimalTriple()
        
        see_also = ResourceRelation(CommuneResource, RDFS.seeAlso)
        
        class Options:
            rdf_type = RDFS.Literal
            resource_uri = 'http://example.org/regionresource/%s/'

Ici aussi, on a typé les données avec les classes de l'INSEE mais on déclare également une relation avec notre précédente classe. On aurait pu s'abstenir, vu que l'on a un moyen grâce au `INSEE_GEO.Region` de trouver un lien entre les deux jeux de données mais ça va nous permettre de naviguer entre les classes plus facilement. On ajoute tout ça à notre graphe de la même manière en ajoutant les relations avec un simple `region_ressource.see_also.add(commune_resource)`.

Calculons maintenant avec ces deux sources de données le nombre de personnes étant soumises à l'ISF croisé avec le montant de la taxe foncière 2009 :

    result = []
    for region_uri in g.subjects(RDF.type, RDFS.Literal):
        region = RegionResource().objects.get(region_uri, store=g)
        rich_people = sum(commune.redevables for commune in region.see_also.all(store=g))
        result.append((region.region, rich_people, region.annee2009))
    result.sort(key=lambda n: n[1], reverse=True)
    print result[:5]
    # -> retourne
    [('ILE-DE-FRANCE', 157597, Decimal('1.27')), 
     ("PROVENCE-ALPES-COTE D'AZUR", 32679, Decimal('2.36')), 
     ('RHONE-ALPES', 18789, Decimal('2.12')), 
     ('PAYS DE LA LOIRE', 8826, Decimal('2.66')), 
     ('NORD-PAS-DE-CALAIS', 8078, Decimal('3.83')), 
     ('MIDI-PYRENEES', 6912, Decimal('4.72')), 
     ('LANGUEDOC-ROUSSILLON', 5955, Decimal('4.86'))
    
Intéressante croissance de la taxe, même si elle s'avère moins vraie dans la suite des résultats, notez au passage qu'il y a autant de personnes (18574) qui sont soumises à l'ISF dans le 16ème à Paris que dans la troisième région du classement (Rhône-Alpes, 18789)... on pourrait encore s'amuser à trouver de fausses conclusions mais le but du billet est plus de montrer la facilité d'utilisation des données une fois liées.

    
## Publication ##

Reste une troisième étape qui est celle de la publication des données, la bibliothèque RDF utilisée permet d'exporter le graphe en de nombreux formats, du RDF/XML au turtle, il serait aussi intéressant d'exposer ces données en RDFa s'il doit y avoir une interface dédiée au navigateur, voire en [JSON-LD](http://json-ld.org/) qui semble plus apprécié des développeurs.

Je n'ai pas encore codé cette partie mais je compte utiliser [piston](http://bitbucket.org/jespern/django-piston/wiki/Home) ou [dj-webmachine](https://github.com/benoitc/dj-webmachine) par exemple. Rien de bien arrêté, d'autant que l'API des Resources est loin d'être finalisée et je m'interroge encore sur les outils à utiliser, lorgnant de plus en plus sur [SuRF](http://packages.python.org/SuRF/) qui facilite une grande partie de ce que j'ai déjà développé. Toute la question est de trouver le bon positionnement du curseur pour qu'un néophyte du web sémantique puisse publier ses données de manière convenable sans se prendre la tête.

Je suis également en train d'étudier le travail de plateforme comme [PublishMyData](http://publishmydata.com/), [DataVerse](http://thedata.org/book/use-and-share-data) ou [Nesstar](http://www.nesstar.com/).


## Participation ##

La meilleure façon de faire avancer les choses est de continuer à chasser des données pour nosdonnees.fr, ajouter une nouvelle source est très simple, j'ai dû le faire pour l'INSEE l'autre soir et ça m'a pris moins de 10 minutes avec une intégration OpenID qui fait plaisir à voir ! La libération des données est bénéfique pour tous, [autant d'un point de vue financier](http://www.fondapol.org/les-travaux/toutes-les-publications/publication/titre/donnees-publiques-gratuites-au-danemark.html) que social. **La France est en train de louper ce train de l'innovation, ne la laissons pas détruire les gares en plus.** Les initiatives citoyennes telles que celles de [Regards Citoyens](http://www.RegardsCitoyens.org/) sont les seules alternatives vu les orientations de l'[APIE](https://www.apiefrance.com/) (qui ne sait pas configurer un certificat SSL au passage...).

Si vous êtes intéressé par le développement (ou l'utilisation future) du projet, n'hésitez pas à me contacter. Je suis convaincu que la libération des données n'est pas suffisante pour une réutilisation de masse et qu'il va falloir leur accorder un peu plus d'attention pour qu'elle soient exploitées à leur meilleur potentiel et enrichies par des acteurs externes.

*Note de dernière minute : un [hackaton est organisé lors de l'OpenDataDay](http://www.opendataday.org/), une bonne occasion de s'y essayer ? :-)*

