Repository with sources and generator of https://larlet.fr/david/ https://larlet.fr/david/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.html 18KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. <!doctype html>
  2. <html lang=fr>
  3. <head>
  4. <!-- Always define the charset before the title -->
  5. <meta charset=utf-8>
  6. <title>★ De l&#39;OpenData au LinkedData : exemple de nosdonnees.fr — Biologeek — David Larlet</title>
  7. <!-- Define a viewport to mobile devices to use - telling the browser to assume that the page is as wide as the device (width=device-width) and setting the initial page zoom level to be 1 (initial-scale=1.0) -->
  8. <meta name="viewport" content="width=device-width, initial-scale=1"/>
  9. <!-- Fake favicon, to avoid extra request to the server -->
  10. <link rel="icon" href="data:;base64,iVBORw0KGgo=">
  11. <link type="application/atom+xml" rel="alternate" title="Feed" href="/david/log/" />
  12. <link rel="manifest" href="/manifest.json">
  13. <link rel="stylesheet" href="/static/david/css/larlet-david-_J6Rv.css" data-instant-track />
  14. <noscript>
  15. <style type="text/css">
  16. /* Otherwise fonts are loaded by JS for faster initial rendering. See scripts at the bottom. */
  17. body {
  18. font-family: 'EquityTextB', serif;
  19. }
  20. h1, h2, h3, h4, h5, h6, time, nav a, nav a:link, nav a:visited {
  21. font-family: 'EquityCapsB', sans-serif;
  22. font-variant: normal;
  23. }
  24. </style>
  25. </noscript>
  26. <!-- Canonical URL for SEO purposes -->
  27. <link rel="canonical" href="https://larlet.fr/david/biologeek/archives/20101130-de-lopendata-au-linkeddata-exemple-de-nosdonneesfr">
  28. </head>
  29. <body>
  30. <div>
  31. <header>
  32. <nav>
  33. <p>
  34. <small>
  35. Je suis <a href="/david/" title="Profil public">David Larlet</a>, <a href="/david/pro/" title="Activité professionnelle">artisan</a> du web qui vous <a href="/david/pro/accompagnement/" title="Activité d’accompagnement">accompagne</a><span class="more-infos"> dans l’acquisition de savoirs pour concevoir des <a href="/david/pro/produits-essentiels/" title="Qu’est-ce qu’un produit essentiel ?">produits essentiels</a></span>. <span class="more-more-infos">Discutons ensemble d’une <a href="/david/pro/devis/" title="En savoir plus">non-demande de devis</a>.</span> Je partage ici mes <a href="/david/blog/" title="Expériences bienveillantes">réflexions</a> et <a href="/david/correspondances/2017/" title="Lettres hebdomadaires">correspondances</a>.
  36. </small>
  37. </p>
  38. </nav>
  39. </header>
  40. <section>
  41. <h1 property="schema:name">★ De l&#39;OpenData au LinkedData : exemple de nosdonnees.fr</h1>
  42. <article typeof="schema:BlogPosting">
  43. <div property="schema:articleBody">
  44. <img src="/static/david/biologeek/images/logos/kamehameha.png" alt="vignette" style="float:left; margin: 0.5em 1em;" property="schema:thumbnailUrl" />
  45. <p>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 <a href="http://www.nosdonnees.fr/">nosdonnees.fr</a> dont je salue l'initiative citoyenne.</p>
  46. <p>L'avantage d'avoir des données réelles est de pouvoir arriver à des analyses marrantes, je me suis donc concentré sur l'<a href="http://www.nosdonnees.fr/package/isf2009">Impôt de Solidarité sur la Fortune par commune 2009</a> et sur le <a href="http://www.nosdonnees.fr/package/taux_fiscalite_2001_2009">Taux de Fiscalité Directe Locale par région 2001-2009</a>. 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 <a href="http://code.google.com/p/google-refine/wiki/GettingStarted">Google Refine</a> (l'ancien Freebase Gridworks) rendent la chose beaucoup plus aisée (tiens <a href="http://lab.linkeddata.deri.ie/2010/grefine-rdf-extension/">une extension RDF</a>).</p>
  47. <h2>Modélisation</h2>
  48. <p>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 :</p>
  49. <pre><code>class CommuneResource(Resource):
  50. region = StringTriple()
  51. departement = StringTriple()
  52. code_commune = StringTriple()
  53. commune = StringTriple()
  54. redevables = IntegerTriple()
  55. patrimoine_moyen = IntegerTriple()
  56. impot_moyen = IntegerTriple()
  57. </code></pre>
  58. <p>À partir de là, je veux pouvoir typer certaines des colonnes de mes données et j'ai connaissance du <a href="http://rdf.insee.fr/geo/">vocabulaire défini par l'INSEE</a> pour justement décrire des régions et département. Une rapide lecture du <a href="http://rdf.insee.fr/geo/regions-2010.rdf">fichier RDF des régions</a> 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) :</p>
  59. <pre><code>INSEE_GEO = Namespace('http://rdf.insee.fr/geo/')
  60. class CommuneResource(Resource):
  61. region = StringTriple(rdf_type=INSEE_GEO.Region)
  62. departement = StringTriple(rdf_type=INSEE_GEO.Departement)
  63. code_commune = StringTriple(rdf_type=INSEE_GEO.code_commune)
  64. commune = StringTriple(rdf_type=INSEE_GEO.Commune)
  65. redevables = IntegerTriple()
  66. patrimoine_moyen = IntegerTriple()
  67. impot_moyen = IntegerTriple()
  68. class Options:
  69. rdf_type = RDFS.Resource
  70. resource_uri = 'http://example.org/communeresource/%s/'
  71. </code></pre>
  72. <p>L'essentiel du travail est fait, je charge tout ça dans un graphe RDF et je peux commencer à jouer avec :</p>
  73. <pre><code>isf_data_filepath = 'data/isf/ISF2009-Tableau 1.csv'
  74. isf_data_file = open(isf_data_filepath, 'r')
  75. g = ConjunctiveGraph()
  76. for line in CommuneResource.loader(isf_data_file, **{'delimiter': ';'}):
  77. line.save(store=g)
  78. line.commune # -&gt; retourne 'CHAMBERY'
  79. </code></pre>
  80. <p>À 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) :</p>
  81. <pre><code>regions = defaultdict(list)
  82. for commune_uri in g.subjects(RDF.type, RDFS.Resource):
  83. commune = CommuneResource().objects.get(str(commune_uri), store=g)
  84. regions[commune.region].append(commune.impot_moyen)
  85. for region, impot_list in regions.items():
  86. regions[region] = sum(impot_list)/len(impot_list)
  87. regions = regions.items()
  88. regions.sort(key=lambda a: a[1], reverse=True)
  89. print regions[:4]
  90. # -&gt; retourne
  91. [('GUYANE', 15044), ('MARTINIQUE', 9579), ('GUADELOUPE', 7379), ('CORSE', 6811)])
  92. </code></pre>
  93. <p>Il semblerait que les riches les plus riches soient en moyenne dans les DOM-TOM (non, la Corse n'en est pas (encore) un ;-)).</p>
  94. <h2>Relations</h2>
  95. <p>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 :</p>
  96. <pre><code>class RegionResource(Resource):
  97. code_region = StringTriple(rdf_type=INSEE_GEO.code_region)
  98. region = StringTriple(rdf_type=INSEE_GEO.Region)
  99. annee2001 = DecimalTriple()
  100. annee2002 = DecimalTriple()
  101. annee2003 = DecimalTriple()
  102. annee2004 = DecimalTriple()
  103. annee2005 = DecimalTriple()
  104. annee2006 = DecimalTriple()
  105. annee2007 = DecimalTriple()
  106. annee2008 = DecimalTriple()
  107. annee2009 = DecimalTriple()
  108. see_also = ResourceRelation(CommuneResource, RDFS.seeAlso)
  109. class Options:
  110. rdf_type = RDFS.Literal
  111. resource_uri = 'http://example.org/regionresource/%s/'
  112. </code></pre>
  113. <p>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 <code>INSEE_GEO.Region</code> 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 <code>region_ressource.see_also.add(commune_resource)</code>.</p>
  114. <p>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 :</p>
  115. <pre><code>result = []
  116. for region_uri in g.subjects(RDF.type, RDFS.Literal):
  117. region = RegionResource().objects.get(region_uri, store=g)
  118. rich_people = sum(commune.redevables for commune in region.see_also.all(store=g))
  119. result.append((region.region, rich_people, region.annee2009))
  120. result.sort(key=lambda n: n[1], reverse=True)
  121. print result[:5]
  122. # -&gt; retourne
  123. [('ILE-DE-FRANCE', 157597, Decimal('1.27')),
  124. ("PROVENCE-ALPES-COTE D'AZUR", 32679, Decimal('2.36')),
  125. ('RHONE-ALPES', 18789, Decimal('2.12')),
  126. ('PAYS DE LA LOIRE', 8826, Decimal('2.66')),
  127. ('NORD-PAS-DE-CALAIS', 8078, Decimal('3.83')),
  128. ('MIDI-PYRENEES', 6912, Decimal('4.72')),
  129. ('LANGUEDOC-ROUSSILLON', 5955, Decimal('4.86'))
  130. </code></pre>
  131. <p>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.</p>
  132. <h2>Publication</h2>
  133. <p>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 <a href="http://json-ld.org/">JSON-LD</a> qui semble plus apprécié des développeurs.</p>
  134. <p>Je n'ai pas encore codé cette partie mais je compte utiliser <a href="http://bitbucket.org/jespern/django-piston/wiki/Home">piston</a> ou <a href="https://github.com/benoitc/dj-webmachine">dj-webmachine</a> 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 <a href="http://packages.python.org/SuRF/">SuRF</a> 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.</p>
  135. <p>Je suis également en train d'étudier le travail de plateforme comme <a href="http://publishmydata.com/">PublishMyData</a>, <a href="http://thedata.org/book/use-and-share-data">DataVerse</a> ou <a href="http://www.nesstar.com/">Nesstar</a>.</p>
  136. <h2>Participation</h2>
  137. <p>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, <a href="http://www.fondapol.org/les-travaux/toutes-les-publications/publication/titre/donnees-publiques-gratuites-au-danemark.html">autant d'un point de vue financier</a> que social. <strong>La France est en train de louper ce train de l'innovation, ne la laissons pas détruire les gares en plus.</strong> Les initiatives citoyennes telles que celles de <a href="http://www.RegardsCitoyens.org/">Regards Citoyens</a> sont les seules alternatives vu les orientations de l'<a href="https://www.apiefrance.com/">APIE</a> (qui ne sait pas configurer un certificat SSL au passage...).</p>
  138. <p>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.</p>
  139. <p><em>Note de dernière minute : un <a href="http://www.opendataday.org/">hackaton est organisé lors de l'OpenDataDay</a>, une bonne occasion de s'y essayer ? :-)</em></p>
  140. </div>
  141. </article>
  142. <footer>
  143. <h6 property="schema:datePublished">— 30/11/2010</h6>
  144. </footer>
  145. </section>
  146. <section>
  147. <div>
  148. <h3>Articles peut-être en rapport</h3>
  149. <ul>
  150. <li><a href="/david/biologeek/archives/20110328-les-outils-manquants-opendata/" title="Accès à Les outils manquants de l&#39;OpenData">Les outils manquants de l&#39;OpenData</a></li>
  151. <li><a href="/david/biologeek/archives/20110322-retour-sur-lopendata-et-nous-et-nous-et-nous/" title="Accès à Retour sur l&#39;OpenData et nous, et nous, et nous ?">Retour sur l&#39;OpenData et nous, et nous, et nous ?</a></li>
  152. <li><a href="/david/biologeek/archives/20101203-un-projet-python-de-lidee-la-publication/" title="Accès à Un projet Python : de l&#39;idée à la publication">Un projet Python : de l&#39;idée à la publication</a></li>
  153. </ul>
  154. </div>
  155. </section>
  156. <section>
  157. <div id="comments">
  158. <h3>Commentaires</h3>
  159. <div class="comment" typeof="schema:UserComments">
  160. <p class="comment-meta">
  161. <span class="comment-author" property="schema:creator">yml</span> le <span class="comment-date" property="schema:commentTime">30/11/2010</span> :
  162. </p>
  163. <div class="comment-content" property="schema:commentText">
  164. <p>Bonjour David,<br />Merci pour cette article extrêmement intéressant, il m&#39;a donné l&#39;envie d&#39;approfondir un peu tes recherches. </p>
  165. <p>Je ne vois pas d&#39;ou vient la classe &#39;Ressource&#39; dont tu hérites pour construire tes &#39;Models&#39;.</p>
  166. <p>Tu parles de piston un peu plus tard dans l&#39;article donc je me demande si c&#39;est un projet django.</p>
  167. <p>Cordialement,<br />--yml<br /></p>
  168. </div>
  169. </div>
  170. <div class="comment" typeof="schema:UserComments">
  171. <p class="comment-meta">
  172. <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">30/11/2010</span> :
  173. </p>
  174. <div class="comment-content" property="schema:commentText">
  175. <p>@yml : elle vient du framework que je suis en train de développer et qui n&#39;a pas (encore) été releasé. Note que ça utilise la syntaxe Django mais la partie modélisation en sera indépendante. La partie publication l&#39;utilisera sûrement cependant.</p>
  176. </div>
  177. </div>
  178. <div class="comment" typeof="schema:UserComments">
  179. <p class="comment-meta">
  180. <span class="comment-author" property="schema:creator">Alexis</span> le <span class="comment-date" property="schema:commentTime">30/11/2010</span> :
  181. </p>
  182. <div class="comment-content" property="schema:commentText">
  183. <p>Super initiative, merci.</p>
  184. <p>Un des gros manques actuel du web semantique semble être une plateforme simple d&#39;utilisation pour permettre de publier des données liées. </p>
  185. <p>Et il semble que tu soit en train de faire l&#39;outil manquant ! Tu pense publier des sources utilisables d&#39;ici le hackfest (ce weekend ici à oxford) ? </p>
  186. <p>Je suis intéressé pour tester l&#39;outil même si l&#39;API n&#39;est pas encore stable: ça permettra peut être de faire des retours utiles.</p>
  187. </div>
  188. </div>
  189. <div class="comment" typeof="schema:UserComments">
  190. <p class="comment-meta">
  191. <span class="comment-author" property="schema:creator">nchauvat</span> le <span class="comment-date" property="schema:commentTime">01/12/2010</span> :
  192. </p>
  193. <div class="comment-content" property="schema:commentText">
  194. <p>@yml, @Alexis: des outils existent déjà... par exemple <a href="http://www.cubicweb.org/">http://www.cubicweb.org/</a> D&#39;autres seront présentés lors de <a href="http://www.semweb.pro/">http://www.semweb.pro/</a></p>
  195. </div>
  196. </div>
  197. <div class="comment" typeof="schema:UserComments">
  198. <p class="comment-meta">
  199. <span class="comment-author" property="schema:creator">David, biologeek</span> le <span class="comment-date" property="schema:commentTime">03/12/2010</span> :
  200. </p>
  201. <div class="comment-content" property="schema:commentText">
  202. <p>@Alexis : merci pour les encouragements ! Je ne pense pas pouvoir publier d&#39;ici ce weekend, je vais essayer de bien avancer dans le train samedi mais j&#39;ai encore un peu trop d&#39;indécisions...</p>
  203. <p>Je peux t&#39;envoyer une archive si tu veux vraiment tester mais sans aucune garantie de rétrocompatibilité...<br /></p>
  204. </div>
  205. </div>
  206. <div class="comment" typeof="schema:UserComments">
  207. <p class="comment-meta">
  208. <span class="comment-author" property="schema:creator">Alexis</span> le <span class="comment-date" property="schema:commentTime">10/12/2010</span> :
  209. </p>
  210. <div class="comment-content" property="schema:commentText">
  211. <p>Ah mince, je viens juste de voir ça, trop tard :) J&#39;attendrais que tu publie pour faire joujou donc !</p>
  212. </div>
  213. </div>
  214. </div>
  215. </section>
  216. <footer>
  217. <nav>
  218. <p>
  219. <small>
  220. Je réponds quasiment toujours aux <a href="m&#x61;ilto:d&#x61;vid%40l&#x61;rlet&#46;fr" title="Envoyer un email">emails</a> (<a href="/david/signature/" title="Ma signature actuelle avec possibilité de chiffrement">signés</a>) et vous pouvez me rencontrer à Montréal. <span class="more-infos">N’hésitez pas à <a href="/david/log/" title="Être tenu informé des mises à jour">vous abonner</a> pour être tenu informé des publications récentes.</span>
  221. </small>
  222. </p>
  223. </nav>
  224. </footer>
  225. </div>
  226. <script src="/static/david/js/larlet-david-3ee43f.js" data-no-instant></script>
  227. <script data-no-instant>InstantClick.init()</script>
  228. </body>
  229. </html>