Initiation à sDNA (Spatial Design Network Analysis) sous Qgis

Ce tutoriel reprend en partie la présentation « Initiation à sDNA sous Qgis » préparée dans le cadre de l’atelier « Analyse de réseaux spatiaux et SIG » organisée par le groupe fmr à SAGEO 2017 (6-9 Novembre, Rouen).

Ces dernières années, plusieurs plugins permettant d’analyser des réseaux spatiaux grâce à Qgis ont été développés ; citons notamment Space Syntax Toolkit (J. Gil et al.), Network Analysis (S. Lhomme), Morpheo (C. Lagesse) ou encore sDNA (C. Cooper, A. Chiaradia, C. Webster). Ces plugins sont souvent utilisés pour analyser des réseaux viaires, mais chacun d’entre eux a une conception différente de la manière de passer de ce réseau à un graphe. Dans ce tutoriel, nous présenterons le plugin sDNA pour Spatial Design Network Analysis. Ce tutoriel est basé sur le manuel du plugin, disponible à l’adresse: http://www.cardiff.ac.uk/sdna/software/documentation.

1) Préalable théorique :

sDNA peut être utilisé pour tout type de réseaux spatiaux, mais la communauté de chercheurs qui l’utilise s’intéresse particulièrement aux réseaux urbains et aux systèmes de transport : réseau de véhicules, réseaux piétons (en intérieur ou en extérieur), cyclistes, transport public. Le plugin peut donc être utilisé aussi bien en planification des transports, en aménagement et en architecture.

Le postulat de ces travaux est que la structure des réseaux permet de capturer des informations sur un très grand nombre de phénomènes : la santé, la cohésion sociale, la valeur du foncier, la « vitalité » d’un quartier, l’usage du sol, les déplacements piétons, la pollution, les accidents ou encore l’insécurité. Ainsi, l’objectif de ces travaux est de fournir des arguments pour la conception de « meilleurs » réseaux dans l’environnement bâti.

sDNA permet d’étudier des réseaux spatiaux, c’est-à-dire des réseaux contenant des informations spatiales. Les nœuds ont donc des positions dans un espace euclidien à deux ou trois dimensions.

L’une des particularités de sDNA, par rapport aux plugins d’analyse de réseaux les plus classiques, est qu’il se focalise sur l’analyse des liens plutôt que des nœuds. Dans le cas des réseaux viaires, les rues sont souvent représentées par la polyligne qui passe par leur centre. Ces polylignes seront donc les liens du réseau, et les carrefours entre les rues seront les nœuds du réseau.

Autre particularité, ces liens ne prennent pas nécessairement la route la plus directe : ils peuvent avoir une forme physique différente de la ligne droite.

sDNA accepte le format « lien-nœud » mais seuls les liens sont réellement indispensables. On peut ainsi importer un shapefile contenant uniquement les rues d’une ville, et l’analyser avec sDNA.

2) Avant de commencer :

Installation :

Préparation du réseau :

A noter que l’analyse des réseaux viaires requiert souvent un travail préalable de nettoyage du réseau. Bien que sDNA propose quelques outils de nettoyage dans « Network preparation », nous recommandons d’utiliser plutôt les outils de nettoyage de vecteurs de Qgis, et de nettoyage topologique d’un réseau disponibles sur OpenJump.

Pour que le réseau soit correctement traité par sDNA, quelques vérifications sont nécessaires :

  • Vérifier le système de projection ;
  • Vérifier la connectivité : pour que deux lignes soient connectées, leurs extrémités doivent coïncider. Dans le cas contraire, sDNA considérera que c’est un pont ou un tunnel, un non-lien ;
  • Vérifier les erreurs de géométrie, particulièrement pour les analyses où les changements de directions sont importants ;
  • Les traffic islands (cf. figure) introduisent un changement de direction et une augmentation de la longueur de ces rues. Pour ne pas en tenir compte (les considérer comme des liens simples) ajouter un champ avec la valeur 1 pour les traffic islands à supprimer et 0 pour le reste. Utiliser ensuite le « Network preparation » de sDNA en cochant la case de correction des traffic islands.

Traffic island (sDNA 3.4.5 documentation)

3) Quelques basiques :

Définitions :

Un lien désigne soit la portion située entre deux jonctions, soit entre une jonction et une extrémité. Une jonction est un point où sont connectées au moins trois liens.

sDNA utilise souvent le terme polyligne plutôt que lien, et ce afin de signifier que ces liens peuvent avoir une forme physique.

Chaque polyligne de sDNA est considérée comme une origine indivisible, tous les flux partants d’une polyligne sont considérés comme émanant de son milieu. En revanche, en tant que destinations, deux choix sont possibles. Dans le Discrete Space Mode, les polylignes sont soit considérées comme des destinations indivisibles. En revanche, dans le Continuous Space Mode, elles sont considérées comme divisibles.

Quelques options :

  • Start grade separation et End grade separation:
    • Cette option permet de choisir une colonne origine et une colonne destination dans la table attributaire. Dans ce cas, seuls les liens qui partagent une origine ou une destination seront connectés.
    • Cette option peut s’avérer utile dans le cas d’un réseau multi-niveaux: rue et métro par exemple. En effet, cela permet de connecter les rues ensemble et les lignes de métro ensemble, en jouant sur les valeurs de grade separation (utiliser par exemple les valeurs 0, 1, 2… pour les rues, et 10, 11, 12… pour le métro).
  • La métrique d’analyse (le type de distance à prendre en compte):
    • Euclidienne= distance ordinaire
    • Angulaire= distance mesurée en termes de changement angulaire, que ce soit sur le même lien ou aux jonctions
    • Customisée= sélectionner un champ de métrique (par exemple un champ avec la distance en temps..)
    • Hybride= nécessite d’écrire la formule correspondante (en utilisant la ligne de commande- voir advanced_config dans le manuel)
    • Métrique prédéfinies= piéton, véhicule, cycliste, transport public, mais dont la formule peut être modifiée en utilisant advanced_config. Les formules sont décrites dans Preset metric variables (cf. manuel)– ce sont des distances pondérées selon les tournants, le trafic, les arrêts (si transport en commun) …
  • La pondération (Weight) :
    • Elle peut se faire sur les liens, les longueurs ou les polylignes.
    • Elle dépend de ce qu’on considère comme quantité importante dans notre analyse: le nombre de liens, la longueur, ou une variable donnée telle que la population, le nombre d’emplois…
    • La pondération recommandée est le nombre de liens, car la densité de liens est connue pour augmenter avec le nombre d’emplois et la population, ainsi en pondérant par le nombre de liens on capture déjà ces informations.

4) Les mesures calculées par sDNA :

Mesures sur les polylignes :

  • Lconn= nombre d’extrémités de lignes connectées à cette ligne
  • Llen= longueur de la polyligne
  • LAC= courbure angulaire cumulée le long de la ligne (en degré)
  • Line Hybrid Metrics= la métrique pour les polylignes est donnée, on peut utiliser cela pour signaler les sens interdits, les escalators, …
  • LSin= longueur de la ligne divisée par la distance à vol d’oiseau entre ses extrémités
  • LBear= écart d’orientation entre les deux extrémités de la ligne
  • Lfrac= proportion du lien que cette ligne représente

Mesures de la forme globale du réseau :

  • HullA= surface de l’enveloppe convexe couverte par le réseau
  • HullP = périmètre de l’enveloppe convexe
  • HullR (convex hull maximum radius)= plus longue distance à vol d’oiseau
  • Convex Hull Bearing= orientation de la plus longue distance à vol d’oiseau (souvent par rapport au nord cardinal)
  • Hull Shape Index= Hull Perimeter au carré, divisé par le produit de 4π et Hull area

Farness, Betweenness et Analyse de détours :

farness :

  • MEDn= longueur moyenne des chemins les plus courts entre ce lien et les autres (farness). C’est l’inverse de la closeness centrality. Cette mesure est pondérée par le poids des destinations.
  • La farness peut être calculée avec les autres types de métrique. Par exemple, MADn= longueur moyenne des chemins qui minimisent le changement de direction entre ce lien et les autres.

Betweenness :

  • BtEn= nombre de chemins les plus courts entre deux liens qui passent par ce lien. La betweenness peut aussi être calculée avec les autres types de métrique.

Analyse de détour : à quel point le réseau dévie des chemins les plus directs ?

  • MCFn= moyenne des distances à vol d’oiseau entre chaque origine et tous les liens.
  • DivEn= proportion de détour = ratio moyen entre plus courts chemins et chemins directs (à vol d’oiseau).

Analyse de voisinage :

  • L’ensemble des mesures évoquées jusqu’ici peut être calculée dans un rayon n choisi. Ceci permet de s’intéresser par exemple aux plus courts chemins, non pas par rapport à l’ensemble du réseau, mais dans le voisinage de chaque polyligne. Ce voisinage dépendra par exemple du type de déplacement (piéton, cycliste, …).
  • Quelques mesures de voisinage :
    • Lnkn= nombre de liens dans un rayon choisi
    • Lenn= longueur du réseau dans un rayon choisi
    • AngDn=courbure totale des liens dans le rayon choisi (en degré)
    • Wtn= poids total dans le rayon choisi

« Learn » et « Predict » :

  • Certaines des mesures calculées par sDNA sont connues pour être corrélées à des phénomènes réels tels que le trafic ou le prix du foncier.
  • Pour repérer ces corrélations, sDNA propose les outils Learn et Predict:
    • Learn = après avoir joint au réseau des données réelles – de population par exemple, Learn prend un échantillon de ces données et crée un modèle qui les relie aux mesures calculées par sDNA ;
    • Predict = permet de prendre le modèle créé par Learn et de l’appliquer à des régions où les données réelles ne sont pas disponibles (en se basant sur les mesures sDNA calculées dans ces régions).

 

Pour citer ce logiciel: Cooper, C., Chiaradia, A., Webster, C. (2016) Spatial Design Network Analysis software, version 3.4, Cardiff University, http://www.cardiff.ac.uk/sdna/

Ryma Hachi

Ryma Hachi est doctorante à l’UMR Géographie-cités depuis octobre 2014, sous la direction de Lena Sanders. Architecte et historienne de formation, sa thèse vise à révéler, grâce à la théorie des graphes, les processus de formation et de transformation des réseaux viaires.

More Posts

1ere séance du cycle de formation à l’analyse de réseaux avec R

La première séance du cycle de formation à l’analyse de réseaux avec R s’est tenue le 24 octobre 2017 à Paris (bâtiment Olympe de Gouges).

La séance était organisée en deux partie. En première partie, une introduction à l’analyse de réseaux a été proposée. Elle revient sur le vocabulaire de la théorie des graphes, montre plusieurs champs d’application dans le domaine de l’analyse des réseaux spatiaux et sociaux et décrit les principales approches  (calculs d’indicateurs locaux, globaux, visualisation, partitionnement, modélisation).

Le diaporama suivant a servi de support pour cette présentation:

 

La seconde partie de la séance était consacrée à la manipulation avec R Studio. L’interface de travail ainsi que les rudiments du langage R ont été présentés puis, pour débuter avec le package igraph, nous nous sommes appuyés sur les supports de cours développés par Rosemonde Letricot en juin 2017 pour l’école d’été d’initiation à l’analyse de réseaux en sciences humaines et sociales organisée par le GDR AR-SHS qui sont téléchargeables au lien suivant:
https://arshs.hypotheses.org/480

La prochaine séance, le 21 novembre 2017, permettra d’aller plus loin dans la manipulation et apprendra à conduire une analyse de réseaux en calculant des indicateurs locaux sur les sommets et sur les liens. Les différents moyens de visualiser les résultats obtenus seront présentés en détail (visualisation de réseaux permettant de faire varier la taille des cercles et des liens, distribution de degrés etc.).

Les inscriptions sont ouvertes. Il suffit d’indiquer votre intérêt pour la formation par mail à l’adresse de Ryma Hachi (hachiryma<a>gmail.com).

La formation se tiendra au même endroit que précédemment de 10h à 12h: salle M019 – Bâtiment Olympe de Gouges, 8 Place Paul-Ricoeur, Paris

Séance du 25 avril 2017 : Geoseastems, un géoportail dédié aux réseaux (maritimes)

La prochaine séance du groupe fmr aura lieu le mardi 25 avril 2017 à 10 h, au 13 rue du four, 5e étage (bibliothèque), à Paris. Elle sera animée par Matthia Bunel, Ingénieur d’études à l’UMR 8504 Géographie-cités (équipe PARIS).

Matthia Bunel présentera le Geoseastems, portail  développé dans le cadre du projet Worldseastems de l’ERC coordonné par César Ducruet (par ailleurs cofondateur du Groupe fmr).

Titre : De la Lloyd’s list à GeoSeastems : développement d’un géoportail spécialisé dans l’étude des réseaux maritimes sur le temps long.

Présentation : Malgré son rôle prépondérant dans le commerce mondial, force est de constater que le transport maritime est longtemps resté le parent pauvre de la géovisualisation et des analyses de réseaux. Mais l’essor des données radar (AIS) et satellitales, disponibles depuis à peine une décennie, a permis à un grand nombre de chercheurs et d’experts de se questionner — à l’aide de méthodologies très variées — sur ce vecteur d’échanges en tous genres (marchandises, personnes, informations, polluants, microbes, …).
Le portail Geoseastems, positionné parmi les solutions existantes, se distingue par trois objectifs majeurs :
(1) il vise à renforcer la dimension analytique par
rapport à la pure visualisation en temps réel fournie par la plupart des projets existants à ce jour ;
(2) il souhaite fournir une approche sur le temps long du commerce maritime, à partir des données inédites de la Lloyd’s qui publie des registres de « mouvements » de navires depuis la fin du 17ème siècle ;
(3) le portail prévoit d’intégrer des données complémentaires sur les autres réseaux de transport (terrestres, aériens), les villes, et celles issues de projets voisins dans le domaine des humanités numériques afin de fournir une plate-forme centralisée et harmonisée à but pédagogique et de recherche, ouverte à tous.
Cette présentation sera l’occasion de discuter du processus de développement du portail, mais également d’aborder tous les problèmes liés au traitement des données de la Lloyd’s en vue de leur intégration à GeoSeastems.

Ces séances ont lieu les mardis matin de 10 heures à midi au 13 rue du Four à Paris, 5e étage, bibliothèque. Elles sont ouvertes à toute personne intéressée mais il peut être prudent de signaler sa venue (groupe_fmr@yahoo.fr), la bibliothèque étant vite remplie.

Françoise Bahoken

Géographe et cartographe, je suis accueillie en détachement depuis décembre 2010 à l'IFSTTAR ; je suis par ailleurs membre-associée de l'UMR 8504 Géographie-Cités.

More Posts

Séance du 21 mars 2017 : Analyse et représentation d’une matrice de flux avec QGIS

La prochaine séance du groupe fmr aura lieu le mardi 21 janvier 2017 à 10h, au 13 rue du four, 5e étage (bibliothèque), à Paris. Elle portera sur l’analyse et la représentation d’une matrice de flux avec QGIS. Elle sera  animée par Françoise Bahoken.

La séance est composée de deux parties. Une première partie donnera lieu à la préparation des données numériques : import de la matrice, calcul d’indicateurs,…, filtrages de champs selon un critère unique global qui sera soit endogène (sélection des valeurs de flux supérieures à une valeur unique) soit exogène (sélection des flux selon un critère de distance continu …). Une seconde partie sera consacrée au tracé de tout ou partie des flux : le choix du figuré sera fonction du type de matrice (asymétrique ou symétrique) et leur représentation fonction des possibilités de symbolisation des liens ou des bandes proposées par QGIS, voire celles de stylisation suggérées par Anita Graser.

Extensions et outils « de flux » disponibles

En pratique, différentes extensions additionnelles et outils intégrés à QGIS peuvent être mobilisés pour représenter des flux. D’après ce que j’ai pu observer, ils diffèrent selon 1) qu’ils mobilisent en entrée une matrice de type liste (format long) ou de type matriciel (format wide) : voir ce billet pour les différents types de matrice asymétriques et celui-ci, pour les symétriques ; 2) aussi selon que le figuré qu’ils permettent de tracer : un lien, plus ou moins large ou une bande, autrement dit à un lien valué dont la surface géométrique est mesurable (ce qui change potentiellement beaucoup de choses, sinon tout sur le plan sémiologique).

N’étant pas spécialiste de QGIS, je ne saurai citer que les outils qui ont attiré mon attention, parmi lesquels, dans le désordre :

  • OURSINS (plugin Oursins), est une extension efficace et opérationnelle proposée par Lionel Cacheux et maintenue sur son dépôt Github ici et qui, comme son nom ne l’indique pas, permet de représenter soit tous les flux soit une sélection d’entre eux ; elle n’est donc pas limitée à la seule représentation dite en « oursins ».
  • MMQgis (Plugin MMQgis), est une extension proposée par Michael Minn (présentée ici) permettant notamment de cartographier des flux. Le groupe fmr a déja mentionné (ici) le tutoriel que Laurent Jégou en a proposé sur le PortailSIG pour réaliser de « jolies cartes de flux ». De mon point de vue, cette application très complète par bien des aspects présente trois inconvénients pour la représentation de flux : 1) elle fonctionne mal sur Mac, ce qui est pour le moins gênant ; 2) elle mobilise deux fichiers de nœuds pour tracer les liens et 3) elle impose la sélection a priori des nœuds d’origine (les hub) alors que ces derniers peuvent justement être révélés par l’analyse statistique, par l’exploration des valeurs de la matrice via leur cartographie puisque l’on est dans un environnement SIG. Cette application permet donc de représenter, à l’échelle interne, des « oursins » soit un phénomène d’accessibilité (des flux selon la focale des lieux de destinations) et non d’interactions … ;
  • FLOWMAPPER plugin for QGIS2x (Plugin FlowMapper), développée par Cem Gulluoglu, correspond à la version QGIS du FLOWMAPPER de Waldo Tobler, sachant que la version pour ArcGis de ce même logiciel ne semble pas (à ma connaissance) été actualisée depuis la version 10x du logiciel. Je suis donc d’emblée plutôt favorable à son utilisation, ce d’autant plus qu’elle permet de représenter les trois principaux types de matrices (asymétriques : les valeurs observées ; symétriques : le volume bilatéral  et antisymétriques : le solde bilatéral). A noter cependant qu’elle nécessite quelques précautions à l’installation : la présence de Python 2.6 (déjà effectuée si vous utilisez également ArcGis) et OGR/GDAL, sinon je vous conseille de suivre les conseils de l’auteur ici, ainsi que ses développements en cours sur son dépôt Github, son installation n’est pas très simple, ce qui peut être pénible ;
  • ARROWS est le nom de deux extensions :
    • La première, très intéressante ne mentionne pas le nom du développeur. Elle permet de générer des bandes orientées (ou non) à partir de liens existants, une documentation bien complète est disponible sur Géoinformations, le site de l’espace interministériel de l’information géographique, accessible ici ;
    • La seconde (Plugin Arrows), développée par Grégoire Piffault est également intéressante : elle permet aussi de générer des bandes orientées à partir de lignes et polylignes (mais je ne l’ai pas testée) ;
  • RTQSPIDER (Plugin rt_qspider), est une extension développée par Giuseppe Sucameli, pour Faunalia, disponible ici sur leur dépôt Github. Cette application est très facile d’accès, simple dans son utilisation et efficace. Elle ne pose aucun problème particulier. Elle permet de tracer des liens (que l’on va ensuite éventuellement pouvoir transformer en bande via ARROWS de Géoinformations ici) à partir d’un unique fichier .csv que l’on va transformer à loisir pour créer des nœuds, des liens et calculer différents attributs grâce aux outils intégrés de QGIS. Si elle peut apparaître comme la moins aboutie par rapport à MMQGIS ou FLOWMAPPER, elle présente l’avantage de la simplicité et de permettre de conserver une très grande liberté dans l’analyse des flux
  • QGIS RESOURCE SHARING (Plugin Resource sharing), une extension développée par Akbar Gumbira pour accéder aux ressources partagées par les utilisateurs, en particulier Flow map arrows proposé par Anita Graser.

Il est possible d’aller plus loin en explorant les ressources disponibles dans la planète QGIS, en particulier celles dédiées aux flux : Movement data in GIS #5: current research topics.

Outils mobilisés dans l’atelier

La séance sera réalisée sur la version en cours de QGIS : 2.18 Las Palmas.

Il est nécessaire de télécharger les extensions suivantes sur le site officiel des plugins QGIS, leur installation pourra être effectuée sur place :

  • pour préparer les données de flux : éventuellement les extensions STATIST et GROUPSTATS ; les outils VECTEUR (éventuellement Geoprocessing / Réallocation de champs) étant déjà intégrés  ;
  • pour tracer des liens et des bandes : RTQSPIDER, ARROWS de Géoinformations,  éventuellement OURSINS ;
  • pour symboliser les liens, les outils intégrés de QGIS seront utilisés ;
  • pour styliser les liens, les ressources de  Anita Graser (en particulier New style flow map et Gradient Arrows) accessibles via le Plugin Resource sharing

Ces séances ont lieu les mardis matin de 10 heures à midi au 13 rue du Four à Paris, 5e étage, bibliothèque. Elles sont ouvertes à toute personne intéressée mais il peut être prudent de signaler sa venue (groupe_fmr@yahoo.fr), la bibliothèque étant vite remplie.

Françoise Bahoken

Géographe et cartographe, je suis accueillie en détachement depuis décembre 2010 à l'IFSTTAR ; je suis par ailleurs membre-associée de l'UMR 8504 Géographie-Cités.

More Posts

10 janvier 2017 : Analyse de graphe avec QGis

La prochaine séance du groupe fmr aura lieu le mardi 10 janvier 2017 à 10h, au 13 rue du four, 5e étage, bibliothèque, Paris. Elle portera sur l’analyse de graphes planaires (mais pas seulement) avec QGIS,
Séance animée par Ryma Hachi et Serge Lhomme.

Cette séance sera décomposée en deux parties : une première partie présentera l’extension nommée « Network_analysis » (http://serge.lhomme.pagesperso-orange.fr/dev.html) ; une deuxième partie sera consacrée à l’extension « Spatial Design Network Analysis » (sDNA) (http://www.cardiff.ac.uk/sdna/).
Dans les deux cas, il faudra bien entendu vous munir d’une version de QGIS (http://www.qgis.org/fr/site/forusers/download.html). A titre d’information, la version 2.14 est parfaitement adaptée.

Pour la partie consacrée à l’extension « Network_analysis », il faudra télécharger le dossier zippé suivant : http://serge.lhomme.pagesperso-orange.fr/data/network_analysis_1_2.zip. Attention, si votre version de QGIS est un peu datée (inférieure à la version2.4), veuillez aussi télécharger ce dossier : http://serge.lhomme.pagesperso-orange.fr/data/network_analysis_1_1.zip. Pour l’installation de cette extension, c’est simple et rapide, elle pourra donc être effectuée juste avant le début de la séance (à 9h45). Les données de cette partie sont téléchargeables en suivant ce lien : https://faust.u-pec.fr/get?k=2slxm3mKYoxMmJY6L1WCette première partie permettra d’aborder les questionnements liés à la différenciation entre effets réseaux et effets spatiaux, de tester les propriétés scale-free d’un graphe, de générer et de « simplifier » des graphes spatialisés (planaires ou non planaires) à partir de données diverses.

La deuxième partie permettra d’aborder plus particulièrement le cas des réseaux intra-urbains (le réseau de rues en l’occurrence), avec la présentation de l’extension sDNA, particulièrement adaptée aux données type OpenStreetMap.
Sur cette page : http://www.cardiff.ac.uk/sdna/software/download/ vous pourrez demander un numéro de série (gratuit), puis télécharger l’extension. Il n’existe pas de version Mac, et il faut a minima QGIS 2.8.
Une fois l’extension téléchargée, son installation est relativement simple (elle pourra également être effectuée juste avant la séance).
L’extension sDNA requiert des données au format SIG (shapefile). Nous ferons un test sur le réseau de rues de Paris au XIXe siècle (données téléchargeables ici : https://filex.univ-paris1.fr/get?k=nsOl3HqSpK0ydOmKjDr). Nous verrons comment préparer le réseau, calculer des indicateurs (de centralité…), puis nous discuterons les résultats.

Analyse des forces et des faiblesses d’un réseau avec R : le package NetSwan

Suite à l’école thématique RESEM « Etudier les réseaux sociaux : espaces, mobilités » qui s’est tenue du 21 au 25 septembre sur l’île d’Oléron, je me suis dit qu’il serait pertinent de sensibiliser davantage d’autres disciplines aux extensions que je développe concernant l’analyse des forces et des faiblesses des réseaux. En effet, durant cette école thématique pluridisciplinaire, seuls des géographes m’ont posé des questions concernant le plugin QGIS que je développe périodiquement. Il faut dire que les SIG restent avant tout utilisés par des géographes et que le plugin en question se focalise sur des réseaux spatiaux !

Ainsi, durant la semaine de pause pédagogique de cette fin d’octobre, j’ai entrepris d’écrire quelques fonctions permettant d’analyser les forces et les faiblesses d’un réseau à l’aide du logiciel R. Puis, afin de diffuser ces fonctions, je n’ai trouvé d’autres solutions que de produire un package. Autant le dire de suite, l’aide est très cursive et rédigée dans un anglais très moyen. Néanmoins, ce package est depuis le 31 octobre disponible sur le CRAN. Une des tâches les plus difficiles a finalement été de trouver un nom à ce package : ce sera finalement NetSwan pour « NETwork Strengths and Weaknesses ANalysis ». Ce package repose sur le package Igraph.

Cette version 0.1 est un ballon d’essai visant à étudier si ces développements sont pertinents en dehors du champ des réseaux spatiaux. Ainsi, seulement quatre fonctions sont proposées actuellement. Une fonction « swan_efficiency » qui se focalise sur la comparaison des valeurs d’éloignement entre le réseau normal et le réseau perturbé. Une fonction « swan_closenness » qui se focalise sur la comparaison des valeurs de proximité entre le réseau normal et le réseau perturbé. Une fonction « swan_connectivity » qui mesure le nombre de relations rendues impossibles lorsqu’un nœud est supprimé. Une fonction « swan_combinatory » qui reproduit les algorithmes d’erreurs et d’attaques développés par Albert et al. [1] [2].

Au-delà des exemples proposés dans l’aide, nous pouvons désormais analyser un réseau social aspatial comme le Zachary Karaté Club et tester sa résistance aux algorithmes développés par Albert et al. Pour cela, il convient de taper le code suivant :

library(igraph)
library(NetSwan)
karate<-make_graph("zachary")

res<-swan_combinatory(karate,10)

Le plus long consiste finalement à produire le graphique représentant les résultats de l’analyse :

plot(res[,1], res[,5], type="o", col="yellow", xlab="Fraction of nodes removed", ylab="Connectivity loss")
lines(res[,1],res[,3], type="o", col="red")
lines(res[,1],res[,4], type="o", col="orange")
lines(res[,1],res[,2], type="o", col="blue")
legend("bottomright",c("Random", "Betweenness", "Degree", "Cascading"),
lty=c(1,1,1,1), pch=c(1,1,1,1),

col=c("yellow","blue","red", "orange"))

Le code final est disponible ici. Il apparaît qu’avec 3 nœuds supprimés, il est possible de perturber plus de 60% des relations de ce réseau, quel que soit le type d’attaques. Seules les erreurs aléatoires produisent des perturbations moins importantes.

Si le besoin s’en fait ressentir, les pistes de développement pour ce package sont nombreuses. Premièrement, ce package ne supporte que les graphes définis avec igraph, il serait tout aussi pertinent de permettre l’analyse de graphes construits avec d’autres packages, comme par exemple statnet. Deuxièmement, les fonctions développées dans NetSwan proviennent avant tout de travaux se focalisant sur l’analyse des forces et des faiblesses de réseaux spatiaux. Il conviendrait de développer plus de fonctions qui tiennent compte des particularités des réseaux non spatiaux.

Serge Lhomme

Maitre de conférences à l'université Paris Est Créteil - Docteur en géographie de l'université Paris VII spécialité "dynamique des milieux et risques"

More Posts - Website

ENPC TD1 : Python (NetworkX) et analyse de réseaux

Dans le cadre d’un enseignement sur les risques à l’Ecole Nationale des Ponts et Chaussées, j’ai élaboré une séquence de cours concernant plus spécifiquement les réseaux techniques. Ce billet propose le résumé d’une séance qui est en lien direct avec les préoccupations de ce blog, puisqu’elle traite de l’analyse de réseaux avec une bibliothèque Python nommée NetworkX. Pour reproduire ce billet, il conviendra bien entendu d’installer Python, puis les bibliothèques : Numpy, Scipy et NetworkX 1.7.

Pour ce billet, nous disposons d’un fichier Python définissant trois listes (ici). Ces listes correspondent à une modélisation simplifiée d’un graphe du réseau autoroutier français. Il convient tout d’abord d’ouvrir ce fichier dans l’IDLE Python, puis d’exécuter ce code. Il est alors possible de construire le graphe du réseau à étudier.

import networkx as nx # import de la bibliothèque networkx
g=nx.Graph() # création du graphe g
g.add_edges_from(arcs) # ajout des arcs de la liste arc au graphe
print(g.nodes()) # consultation des nœuds du graphe g

print(g.edges()) # consultation des arcs du graphe g

Plusieurs indicateurs pouvant nous servir par la suite peuvent être calculés. Pour chaque nœud, on peut ainsi calculer le degré, la centralité intermédiaire ou l’intermédiarité (betweenness), l’éloignement moyen.

deg=nx.degree(g) # calcul des degrés des nœuds du graphe g
bet=nx.betweenness_centrality(g)  # calcul des centralités
aspl=nx.average_shortest_path_length(g)

print(aspl) # consultation des éloignements moyens

Si l’on souhaite afficher le nom des villes suivant leur niveau de centralité intermédiaire, il est possible d’utiliser la bibliothèque Operator :

import operator # import de la bibliothèque operator
tri=sorted(bet.items(), reverse=True, key=operator.itemgetter(1))
for i in range(len(tri)): # boucle affichant le nom des villes

    print(villes[int(tri[i][0])])# en fonction de l’opération de tri

Pour analyser les forces et les faiblesses d’un réseau, il faut le mettre à l’épreuve. Pour cela, il existe un moyen radical : supprimer des éléments du réseau et recalculer des indicateurs calculés au préalable afin de comparer les résultats obtenus. Pour un réseau transport, l’éloignement moyen peut être considéré comme un indicateur pertinent. Afin de conserver un graphe « intact », il peut être pertinent de travailler sur un deuxième graphe créé pour être perturbé.

g2=nx.Graph() # création du graphe g2 créé pour être perturbé
g2.add_edges_from(arcs) # le graphe g2 est au départ identique au graphe g
g2.remove_node(20) # suppression du nœud 20
aspl2=nx.average_shortest_path_length(g2) # calcul sur le graphe perturbé
vul20=aspl2-aspl # comparaison des valeurs obtenues pour un même indicateur

print(vul20) # affichage du résultat correspondant à la perturbation engendrée par la suppression du nœud 20

Dès lors, il est possible de généraliser ce calcul pour l’ensemble des nœuds du réseau étudié :

vul=[] # création du tableau qui contiendra les résultats
for i in range(len(g.nodes())): # boucle de la taille du nombre de nœuds
   g2=nx.Graph() # à chaque itération, création du graphe g2 qui sera perturbé
   g2.add_edges_from(arcs) # le graphe g2 est au départ identique au graphe g
   g2.remove_node(i) # à chaque itération, suppression d’un nœud (le numéro i)
   aspl2=nx.average_shortest_path_length(g2) # calcul sur le graphe perturbé
   dif=aspl2-aspl # à chaque itération on calcule la perturbation engendrée

   vul=vul+[(i,dif)] # ajout dans le tableau de la valeur obtenue pour un nœud supprimé

Néanmoins, à partir du 8ème élément (print(vul)), une erreur se produit. En effet, retirer un ou plusieurs éléments à un réseau n’est pas une opération neutre. En l’occurrence, certains éléments, s’ils sont retirés, peuvent notamment engendrer des ruptures de connexité (des pertes de connectivité). Ici, les ruptures de connexité engendrent l’impossibilité de calculer l’éloignement moyen puisque certaines distances deviennent incalculables (ces distances tendent en fait vers l’infini). Il faut alors créer des exceptions.

vul=[] # création du tableau qui contiendra les résultats
for i in range(len(g.nodes())): # même code que ci-dessus
   if (i!=8 and i!=53): # on rajoute juste deux exceptions
      g2=nx.Graph()
      g2.add_edges_from(arcs)
      g2.remove_node(i)
      aspl2=nx.average_shortest_path_length(g2)
      dif=aspl2-aspl

      vul=vul+[(i,dif)]

Comme précédemment, il est possible de trier les résultats obtenus afin d’identifier les éléments les plus problématiques. Il apparaît alors que ces éléments sont presque identiques à ceux apparaissant comme les plus centraux.

On peut alors chercher à mesurer le degré d’intensité de la relation existant entre la « vulnérabilité » et la « centralité » d’un nœud. Pour cela, il convient de trouver un indicateur de vulnérabilité pouvant se calculer pour tous les nœuds. NetworkX propose un indicateur qui fonctionne comme nous avons procédé, mais en utilisant un indicateur de proximité (closeness) et non d’éloignement, ce qui permet de palier au problème de rupture de connexité (en effet, en s’appuyant sur les inverses des distances, les valeurs tendant vers l’infini tendent vers zéro).

import numpy
vul2=nx.closeness_vitality(g) # calcul de vulnérabilité avec networkx
mvul2=list(vul2.items()) # transforme la bibliothèque en une liste
avul2=numpy.array(mvul2) # transforme cette liste en une array
x=avul2[:,1] # la variable x correspondant à la vulnérabilité des nœuds
mbet=list(bet.items()) # transforme la bibliothèque en une liste
abet=numpy.array(mbet) # on transforme cette liste en une array
y=abet[:,1] # la variable y correspondant à la centralité des nœuds
from scipy import stats
slope, intercept, r_value, p_value, std_err = stats.linregress(x,y)
r2=r_value*r_value # calcul du r2

print(r2) # affichage du r2

A la fin, on obtient un code propre que vous pouvez télécharger ici. S’il existe un lien entre vulnérabilité et centralité, celui-ci est imparfait. On essaiera de le montrer dans un prochain billet relatif à ces enseignements.

Serge Lhomme

Maitre de conférences à l'université Paris Est Créteil - Docteur en géographie de l'université Paris VII spécialité "dynamique des milieux et risques"

More Posts - Website

Cartographie de flux avec le package cartography

Depuis hier, le package cartography développé par nos collègues Timothée Giraud (http://rgeomatic.hypotheses.org/) et Nicolas Lambert (http://neocarto.hypotheses.org), tous deux ingénieurs à l’UMS RIATE, est en ligne sur le CRAN. Il permet de faire beaucoup de cartes différentes dont des cartes de flux. Le script et les données utilisés dans ce billet sont téléchargeables ici (j’arrête provisoirement github, j’y fais un peu n’importe quoi…) – le script est encodé en utf-8 (pour règler l’encodage dans RStudio : Tools > Global Options > Default text encoding).

La documentation sur les fonctions est lisible et homogène et les exemples fonctionnent mais, bien entendu, ce qui est intéressant est de pouvoir importer et cartographier ses propres données. Deux fichiers sont attendus en entrée : une liste de liens (origine, destination, intensité de la relation) et un fond de carte au format .shp. Les deux donneront dans R un data.frame et un  SpatialPolygonsDataFrame (noté SPDF dans la suite du  billet). Il est bien entendu nécessaire que le code des unités spatiales soit le même dans les deux fichiers…

La première étape consiste à attribuer des coordonnées à la liste de liens à l’aide de la fonction getLinkLayer. La fonction prend comme argument : le nom du SPDF et les colonnes origine-destination du data.frame. Si l’identifiant commun n’est pas la première colonne du SPDF, il est nécessaire de préciser la colonne adéquate avec l’argument spdfid = « Nom_de_la_colonne ». Dans l’exemple téléchargeable, l’argument est utilisé.

La fonction plot(SPDF) permet ensuite de visualiser le fond de carte.

Deux fonctions permettent de cartographier les flux : propLinkLayer (épaisseur proportionnelle à l’intensité) et gradLinkLayer (flux discrétisés). Le script téléchargeable devrait vous permettre de comprendre la logique d’utilisation et les principales options. Dans les deux cas, l’orientation des flux n’apparaît malheureusement pas.

Signalons enfin la fonction legendPropLines qui permet de personnaliser la légende de la carte réalisée (fonction non encore testée à ce jour).

La réalisation de cartes est plutôt aisée mais, comme toujours pour la cartographie des flux, cela ne garantit pas nécessairement un résultat d’une grande lisibilité…

 

 

Analyse de réseau avec QGIS 2.x

Voici un cinquième billet concernant l’analyse de réseau et QGIS (billet 1, billet 2, billet 3, billet 4), car une certaine demande semble apparemment exister. En effet, lorsque que l’on consulte les statistiques du site, les pages relatives à QGIS et à la programmation Python apparaissent parmi les pages les plus visitées (pas autant que les pages relatives à R, mais quand même…). Les plugins présentés dans ces billets sont donc régulièrement téléchargés. En outre, comme quelques mises à jour ont été effectuées (les trois premiers plugins ont notamment été regroupés et complétés), il semble nécessaire de vous proposer un nouveau lien pour le télécharger (cliquez ici pour la télécharger).

Si vous voulez disposer à tout moment de la version la plus récente du plugin, je vous conseille de télécharger la version disponible sur mon site personnel (http://serge.lhomme.pagesperso-orange.fr/dev.html). Pour installer cette extension, il vous faudra tout d’abord dézipper le dossier téléchargé, puis copier le dossier « network_analysis » dans « C:\Program Files\QGIS xxxx\apps\qgis\python\plugins\ ». Lancez QGIS et le tour est joué !

Une seule chose est à vérifier. Lorsque vous dézippez un dossier sous Windows, il vous est proposé de l’envoyer vers un dossier du même nom. Ainsi, votre dossier « network_analysis » est lui-même dans un dossier « network_analysis ». Il faut éviter cette redondance. Ainsi, après avoir copié le dossier, vérifiez bien que votre arborescence est la suivante «C:\Program Files\QGIS xxxx\apps\qgis\python\plugins\network_analysis» et pas «C:\Program Files\QGIS xxxx\apps\qgis\python\plugins\network_analysis\network_analysis».

Pour effectuer les calculs, il est nécessaire de disposer de deux couches d’information géographique : une première couche contenant les nœuds et une deuxième couche contenant les arcs. La couche des nœuds doit impérativement contenir les identifiants des nœuds. Il est conseillé de choisir comme identifiant des valeurs numériques allant de 1 à n (vous pouvez télécharger un exemple ici). De même, la couche des arcs doit posséder un champ « identifiant ». De surcroît, cette couche doit impérativement comporter deux champs qui renvoient aux identifiants des nœuds formant les extrémités des arcs (vous pouvez télécharger un exemple ici).

Le fonctionnement de cette extension est très simple. Premièrement, elle repose sur une décomposition en trois familles d’algorithmes : les algorithmes de base (Degree ; Closeness ; Betweenness ; Eloignement ; Clustering ; Eigenvector ; Square_clustering ; Eccentricity ; Page rank ; Average neighbor degree ; Hub_dependence) ; les algorithmes de vulnérabilité (Allongement moyen ; Closeness vitality ; Dynamique ; Combinatoire) ; un algorithme de détection de communautés (Méthode de Louvain). Vous avez accès à ces familles d’algorithmes via le menu QGIS dans « Extension → Analyse de graphe… ».

qgisv2

Après avoir choisi votre famille d’algorithmes, une boite de dialogue s’ouvre vous demandant de sélectionner votre couche contenant les nœuds et votre couche contenant les arcs. Puis, après avoir appuyé sur OK, une deuxième boite de dialogue s’ouvre permettant de renseigner les principales propriétés de votre graphe. Il est alors possible de sélectionner le champ correspondant aux nœuds de départ, le champ correspondant aux nœuds d’arrivée, d’indiquer si le graphe est orienté ou non et si le réseau est valué ou non. Si le réseau est valué, il vous faudra indiquer le champ correspondant à cette valuation. Enfin, il faut choisir l’algorithme que vous souhaitez appeler et si les résultats doivent être normalisés ou non.

boite

A l’heure actuelle tous les calculs se font sur les nœuds. A l’avenir, les calculs sur les liens seront proposés. Ainsi, une fois que vos choix ont été validés, puis les calculs exécutés, les résultats sont stockés dans la table comportant les nœuds. Grâce à QGIS, vous pouvez alors effectuer des discrétisations ou des caractérisations à partir du champ créé.

A noter que seul le calcul de vulnérabilité combinatoire diffère de ce fonctionnement général avec une boite de dialogue supplémentaire à compléter et des résultats stockés sous la forme d’une image.

Serge Lhomme

Maitre de conférences à l'université Paris Est Créteil - Docteur en géographie de l'université Paris VII spécialité "dynamique des milieux et risques"

More Posts - Website

Sélection de flux avec R : le package flows

L’analyse et la représentation des flux supposent le plus souvent une sélection de ces derniers. La méthode classique en analyse spatiale des flux dominants (voir ce billet) et les méthodes des flux majeurs (garder x premiers flux par lieu, les x flux les plus install.packages(« flows », dependencies = TRUE)importants en volume, les flux représentant plus de x % d’un volume donné etc.) forment les deux grandes familles de sélection couramment employées.

Le package flows, développé en juin-juillet au sein de l’UMS RIATE avec mes collègues Timothée Giraud (voir le blog rgeomatic) et Marianne Guérois permet de choisir ses critères de sélection, de calculer des indicateurs statistiques et, pour les flux dominants, de visualiser la sélection effectuée. Il est disponible sur le CRAN donc s’installe normalement avec un install.packages(« flows », dependencies = TRUE).

Les fonctions de sélection et d’analyse prennent en entrée une matrice carrée origine – destination. Si les données de départ sont de type origine – destination – intensité, la fonction prepflows permet de passer en format carré.

La fonction statmat fournit pour une matrice origine destination un certain nombre d’éléments utiles pour connaître les données de départ mais également justifier statistiquement la méthode ou le seuil retenu. On obtient notamment le volume total de flux et les indicateurs statistiques de base sur ces flux (quartiles, moyenne, etc.), la densité, le nombre, la taille et la composition des composantes connexes, le degré pondéré et non pondéré des sommets. Des sorties graphiques permettent d’étudier la distribution des degrés (pondérés et non pondérés) et des liens (boîte à moustache et courbe de Lorenz sur l’intensité des flux).

En ce qui concerne la sélection, trois familles de méthode sont implémentées :

  • flux dominants classiques ;
  • critères locaux (pour chaque sommet, garder les flux obéissant à tel critère) ;
  • critères globaux (considérant l’ensemble des flux, garder ceux obéissant à tel critère).

La fonction compmat permet de comparer deux matrices origine-destination de même dimension, notamment pour connaître la perte d’information consécutive à une sélection.

Enfin, la représentation graphique est limitée aux résultats issus de la méthode des flux dominants dans la mesure où d’autres packages existent pour visualiser des graphes valués ou réaliser des cartes de flux (un package de cartographie développé par le déjà cité Timothée Giraud et son collègue Nicolas Lambert devrait être disponible très prochainement). Elle permet d’obtenir des figures de ce type :

exemplemap

Exceptionnellement, je ne fournis pas de script : je me permets de renvoyer à la vignette qui accompagne le package et qui explique tant les méthodes que l’utilisation des fonctions.

Dernier point : n’hésitez pas à envoyer vos remarques (par mail ou en commentaire), demandes et critiques, pour que nous puissions améliorer ce package.

Combinaisons de graphes avec igraph

Ayant eu récemment à me pencher sur la question délicate des jointures entre réseaux ayant des liens en communs, j’en profite pour faire un point rapide sur les fonctions du package igraph permettant ces combinaisons. Si vous ne connaissez pas ce package, voyez les billets taggés R sur ce blog. Le script utilisé est téléchargeable sur github ici-même. et, pour les personnes qui n’ont pas envie et/ou besoin de s’inscrire sur ce git, il est reproduit à la fin de ce billet.

Soit les deux graphes suivants :

g1g2

g1 et g2 sont deux graphes non orientés et ont trois sommets (ACD) et deux liens en commun (pléonasme…). Toutes les fonctions commentées ici prennent des graphes de même type en entrée (orientés ou non), certaines autorisent les combinaisons de graphes différents (un orienté et un non orienté).

La première étape du script consiste à créer des attributs pour g1 et g2 : attributs du réseau, des sommets et des liens. L’objectif est de contrôler la perte éventuelle d’attributs lors des opérations effectuées. Pour mémoire, pour un graphe nommé g, la syntaxe de igraph est la suivante :

g$densite <- graph.density(g) #attribut du graphe
V(g)$degre <- degree(g) #attribut des sommets (V comme vertex)
E(g)$betw <- edge.betweenness(g) #attributs des liens (E comme edges)

La fonction graph.complementer prend un graphe en attribut et crée son complémentaire : les liens absents dans le graphe de départ sont créés, les liens présents dans le graphe de départ sont supprimés. Un paramètre booléen loops permet de décider si on souhaite ou non générer des boucles (par défaut, loops = FALSE). Les attributs du réseau et des sommets sont conservés, les attributs des liens sont perdus (ce qui semble cohérent, ces liens étant supprimés).

La fonction graph.difference prend deux graphes en entrée : seuls les liens présents dans le premier réseau et non dans le deuxième sont conservés, tous les attributs du premier réseau sont gardés. On peut également utiliser l’opérateur %m% (comme minus) : écrire graph.difference(g1,g2) ou g1%m%g2 est donc équivalent. La figure ci-dessous permet de comparer les résultats obtenus : le lien AD présent dans g1 et g2 donc les opérations g1-g2 et g2-g1 entraînent sa disparition.

graph_difference

La fonction (non utilisée dans le script) graph.disjoint.union permet de joindre deux graphes dont les sommets sont distincts.

La fonction graph.intersection (ou %s%) prend deux graphes en entrée et un paramètre keep.all.vertices (TRUE par défaut) permet de choisir entre garder l’ensemble des sommets et les seuls sommets connectés dans le graphe produit. Les attributs sont tous conservés. Enfin, la fonction graph.union prend deux graphes en entrée : tout lien présent dans un moins un graphe
sera dans le graphe produit – sans création de liens multiples – et tous les attributs seront conservés.

Script complet

library(igraph)

#création des deux graphes
d1 <- rbind(c(« A », »B »),c(« A », »C »), c(« B », »C »), c(« A », »D »), c(« D », »E »))
d2 <- rbind(c(« A », »F »), c(« A », »D »), c(« A », »C »), c(« C », »I »))

g1 <- graph.data.frame(d1, directed = FALSE)
g2 <- graph.data.frame(d2, directed = FALSE)

#visualisation
par(mfrow = c(1,2))
plot(g1, main = « g1 »)
plot(g2, main = « g2 »)
dev.off()

#3 points communs (ACD) et deux liens communs (AD et AC)

#ajout d’attributs : degré des sommets
V(g1)$degree <-  degree(g1)
V(g2)$degree <- degree(g2)

#ajout d’attributs aux liens (intermédiarité)
E(g1)$between <- edge.betweenness(g1)
E(g2)$between <- edge.betweenness(g2)

#ajout d’attributs au réseau : densité
g1$densite <- graph.density(g1)
g2$densite <- graph.density(g2)

#fonction graph.complementer
#crée un graphe avec les liens absents du graphe de départ
#les liens présents au départ sont eux supprimés
compg1 <- graph.complementer(g1, loops = FALSE)
plot(compg1)
#les attributs des sommets et du graphe sont conservés
#attributs des liens sont perdus
V(compg1)$degree
compg1

#fonction graph.difference premier réseau – deuxième réseau (opérateur %m%)
#seuls les liens présents dans le premier réseau et non dans le deuxième sont conservés
#tous les attributs du premier réseau sont gardés
diffg1 <- graph.difference(g1, g2)
diffg1

diffg2 <- graph.difference(g2, g1)

#coordonnées (quasi) identiques pour faciliter la comparaison
lay <- layout.fruchterman.reingold(g1)

#lien A-D présent dans g1 et g2 donc g1-g2 et g2-g1 entrainent sa disparition
par(mfrow = c(2,2))
plot(g1, main = « g1 », layout = lay, vertex.color = « red », vertex.size = 20, vertex.label.dist =3)
plot(g2, main = « g2 », layout = lay, vertex.color = « red », vertex.size = 20, vertex.label.dist =3)
plot(diffg1, main = « graph.difference(g1, g2) », layout = lay, vertex.color = « red », vertex.size = 20, vertex.label.dist =3)
plot(diffg2, main = « graph.difference(g2, g1) », layout = lay, vertex.color = « red », vertex.size = 20, vertex.label.dist =3)
dev.off()

#calculer degré actualisé
V(diffg1)$degree2 <- degree(diffg1)
V(diffg1)$degree
V(diffg1)$degree2

#graph.disjoint.union pour joindre des graphes dont les sommets différents (opérateur %du%)

#liens communs à deux graphes : conserve les attributs
#tous les sommets sont conservés (opérateur %s%)
interg1 <- graph.intersection(g1,g2, keep.all.vertices = TRUE)

#seuls sommets adjacents aux liens communs sont gardés (ici ACD)
interg2 <- graph.intersection(g1,g2, keep.all.vertices = FALSE)

par(mfrow = c(1,2))
plot(interg1, sub = « graph.intersection(g1,g2,\nkeep.all.vertices = TRUE) »)
plot(interg2, sub = « graph.intersection(g1,g2,\nkeep.all.vertices = FALSE) »)
dev.off()

#union de graphes : tout lien présent dans un moins un graphe
#est dans le graphe produit – tous les attributs sont conservés
#ne crée pas de liens multiples

uniong <- graph.union(g1,g2) #opérateur %u%

Analyse (et visualisation) de réseaux avec NetworkX – 1

NetworkX est un module python dédié à l’analyse de réseaux. Déconseillé comme outil d’apprentissage, il pourrait s’avérer utile pour des personnes plus expertes, notamment pour l’analyse des réseaux bipartis où le choix reste limité (y compris dans R, le package bipartite n’intégrant guère de mesures récentes). Avant de découvrir NetworkX, il est indispensable de savoir utiliser Python (écriture et exécution d’un script .py). Il exige des centaines de ressources en ligne sur le sujet et je n’ai pas les compétences permettant d’en recommander une en particulier. Mais j’insiste : soit vous cherchez des mesures non existantes ailleurs (ex. plus courts chemins dans des graphes bipartis), vous avez des notions en programmation et l’investissement peut être rentable ; soit vous vous en sortez très bien avec vos logiciels préférés et, à moins d’animer un blog sur l’analyse de réseaux, l’investissement n’est peut-être pas tout à fait nécessaire… Je précise pour finir que je suis encore très loin de maîtriser l’outil (je n’arrive par exemple pas encore à importer des attributs pour les sommets) et que ce billet est très perfectible. Les phrases suivies d’un * sont à considérer comme de possibles erreurs de ma part…

Comment ça marche ?

Le principe est simple : écrire un script python utilisant les commandes du module NetworkX et l’exécuter. Le graphe analysé peut être créé à l’intérieur même du script ou généré à partir d’une liste de liens, et l’importation de fichiers d’autres formats sont également possibles (GEXF, GraphML, Pajek, JSON etc. voir la liste complète ici). Les résultats et les sorties graphiques peuvent être exportés pour être utilisés dans un environnement logiciel autre (format image ou vectoriel).

Comme souvent, il est possible de générer directement son réseau (sommets, liens et attributs éventuels pour les deux) dans NetworkX. Et comme presque toujours, cette option (longuement développé dans tous les tutoriels disponibles) n’a à peu près aucun intérêt dans la mesure où nos données sont déjà existantes dans un format quelconque… Je vais prendre ici le cas le plus simple – ou en tout cas, l’un des plus répandus – dans la mesure où les données sont sous la forme d’une liste de liens possédant un attribut (intensité). Représenter un sommet isolé oblige à créer une boucle.  NetworkX permet également de lire des fichiers structurés sous la forme :

a b c

b d e  etc. où le premier sommet est l’origine du lien et les sommets suivants ses voisins (fonction networkx.read_adjlist).

J’ai utilisé un format .txt et des espaces comme séparateurs pour créer un fichier liens.txt. Les attributs doivent être indiqués entre accolades. S’il n’y a pas d’attribut pour les liens, n’importe quel séparateur semble utilisable (j’ai testé avec des virgules et des points-virgules) ; par contre, s’il y a un attribut, l’espace semble être le séparateur à utiliser*. Le nom des attributs est entre apostrophes, suivi de :, puis de la valeur. S’il y a plusieurs attributs, les séparer par une virgule comme dans l’exemple ci-dessous.

a b {'weight':2, 'color':'green'}
a c {'weight':3, 'color':'green'}

Par défaut, NetworkX considère que le graphe est non orienté. Le script suivant permet d’importer le fichier, de le visualiser, d’effectuer des mesures standards, de le rendre non-orienté puis d’effectuer une nouvelle série de mesures. Le script et le fichier des liens sont téléchargeables sur github à l’adresse https://github.com/beauguitte/NetworkX_1. Je le reproduis in extenso pour les personnes ne souhaitant pas s’inscrire sur le git en question…

Exemple type

# coding: utf8
import networkx as nx
import matplotlib.pyplot as plt #pour la visualisation

#lecture d'une liste de liens
G=nx.read_edgelist("liens.txt", create_using=nx.DiGraph())

#visualisation
pos=nx.spring_layout(G)               #choix de l'algorithme
nx.draw(G, pos, with_labels=True)
plt.show()
#plt.savefig("edge_colormap.png")     #export dans un format donné

Ce qui donne à peu près ça.

plot_ networkx

#analyse
print "number of nodes : %s" % G.number_of_nodes()
print "number of edges : %s" % G.number_of_edges()
print G.in_degree()
print G.out_degree()

#passage en graphe non orienté
G=G.to_undirected()

print "degree of node a : %s" % G.degree("a") #sommet spécifique
print "degree (undirected) %s" % G.degree()   #ensemble des sommets
print "betweenness centrality %s" % nx.betweenness_centrality(G)
print "degree (normalized) %s" % nx.degree_centrality(G)

#transformer les mesures en attributs
deg = G.degree()
nx.set_node_attributes(G, 'degree', deg)

#visualisation
pos=nx.spring_layout(G)
nx.draw(G, pos, with_labels=True)
plt.show()

#recherche des cliques
cl = list(nx.find_cliques(G))
print "cliques %s" % cl

#plus courts chemins
pathlengths=[]

print("source vertex {target:length, }")
for v in G.nodes():
   spl=nx.single_source_shortest_path_length(G,v)
    print('%s %s' % (v,spl))
    for p in spl.values():
        pathlengths.append(p)
print('')
print("moyenne des plus court chemins %s" % (sum(pathlengths)/len(pathlengths)))

#distribution des plus courts chemins
dist={}
for p in pathlengths:
    if p in dist:
        dist[p]+=1
    else:
        dist[p]=1

print('')
print("length #paths")

print("densité : %s" % nx.density(G))
print("diamètre: %d" % nx.diameter(G))
print("eccentricity: %s" % nx.eccentricity(G))
print("centre : %s" % nx.center(G))
print("péripherie : %s" % nx.periphery(G))

En attendant la suite…

Sur le papier, NetworkX est plutôt séduisant pour l’analyse des graphes bipartites et/ou valués. En pratique, le coût d’entrée pour un programmateur peu avancé est assez élevé. Je compte m’acharner dessus dans les semaines à venir et proposer un billet un peu moins approximatif que celui-ci aussi vite que mes capacités d’apprentissage le permettront.

Et pour aller plus loin

Site : https://networkx.github.ioe/

La documentation disponible en ligne est exclusivement en anglais : voir par exemple la très bonne présentation de Derek Greene à cette adresse : http://mlg.ucd.ie/files/summer/tutorial.pdf

R et igraph – revue de détail

Suite du billet consacré à statnet il y a peu ici même.

L’objectif du billet est le même que pour celui signalé une ligne au dessus : importer et effectuer une série de mesures sur le graphe suivant. Téléchargement des fichiers de données et du script commenté ici (encodage en utf-8).
td1plot

Importer ses données

L’importation des données avec igraph est plus simple dans la mesure où les liens et les attributs des sommets peuvent être importés simultanément à l’aide de la commande graph.data.frame – et la gestion des sommets isolés ne pose donc aucun problème particulier. En ce qui concerne les liens, igraph considère que les deux premières colonnes correspondent aux origines et aux destinations, toutes les éventuelles colonnes suivantes seront considérées comme des attributs des liens.

Après transformation, taper le nom de l’objet créé permet d’obtenir à peu près les mêmes informations qu’avec statnet mais sous une forme un peu moins lisible.

library(igraph)

liens <- read.table("liens2.txt", header=TRUE, sep=";")
attributs <- read.table("attributs.txt", header=TRUE, sep=";")

g <- graph.data.frame(liens, vertices = attributs)
g
IGRAPH DN-- 7 8 -- 
+ attr: name (v/c), ANC (v/n), TEAM (v/c), I (e/n)

g est donc un objet igraph de type directed network (DN), comprenant 7 sommets et 8 liens. Il existe 3 attributs pour les sommets (attr v), deux de type character (name et TEAM), un de type numeric (ANC (v/n)) ; un attribut numérique I pour le lien.

Filtrer les données

Sélectionner une partie du graphe en fonction d’un attribut (des sommets et/ou des liens) est d’une grande simplicité une fois comprise la logique de la syntaxe.

Si je souhaite supprimer les liens d’une intensité inférieure à 2

g2 <- delete.edges(g,E(g)[I<2])

soit, si l’on décompose, la fonction delete.edges (nom du graphe, E comme edge (nom du graphe) [ attribut et condition]). Si l’on veut supprimer des sommets, on remplace par delete.vertices et E devient V comme ici où je supprime les sommets de degré inférieur à 2.

g22 <- delete.vertices(g, V(g)[degree(g) < 2])

Si l’on supprime des sommets, les liens adjacents disparaissent également. Si l’on supprime des liens, par défaut, tous les sommets du graphe restent présents.

Mesurer

La recherche de la densité (graph.density) donne – et c’est heureux – le même résultat que statnet, soit 0.19 (8/42).

La recherche des composantes connexes (clusters) renvoie une liste de 3 objets, l’appartenance des sommets (les sommets abcd sont dans la composante connexe numérotée 1), la taille des composantes connexes et leur nombre.

$membership
[1] 1 1 1 1 2 2 3
$csize
[1] 4 2 1
$no
[1] 3

Contrairement à statnet, igraph considère par défaut que la connexité faible est recherchée. Pour la connexité forte, il faudra utiliser l’option mode = « strong ».

La recherche des cliques (fonction cliques) est ici efficace mais, par défaut, igraph fournit toutes les cliques de taille 1, 2 etc. Il est donc conseillé d’indiquer un min. La direction des liens n’est pas et ne peut pas être prise en compte. Les commandes dyad.census et triad.census donnent exactement les mêmes résultats que statnet mais la présentation est moins claire ici pour cette dernière fonction car les codes MAN ne sont pas rappelés. Le résultat reproduit ci-dessous n’est pas très simple à interpréter.

 triad.census(g) 
 [1] 11 12  8  0  1  0  1  0  1  0  0  0  1  0  0  0

La commande degree fournit par défaut est la somme des degrés entrants et sortants. La normalisation (normalized = TRUE) se fait ici par le degré maximal possible. Si je calcule les degrés entrants normalisés, j’obtiens le résultat suivant :

 degree(g, mode="in", normalized = TRUE) 
    a         b      c     d      e       f         g 
0.1666667 0.3333333 0.50 0.000 0.1666667 0.1666667 0.00

Le degré entrant de c est 3, il y a 7 sommets donc le maximum possible est de 6 (pas de boucle) : c a donc un degré entrant normalisé de 0.5. La fonction degree.distribution donne la proportion de sommets ayant un degré zéro, un, deux etc. Mais si on visualise avec la fonction plot(degree.distribution(g)), l’axe des abscisses est faux (il commence à un au lieu de zéro). Et si vous faites partie des adeptes du scale-free, le fameux plot log log s’obtient aisément (voir le script) mais il n’a évidemment aucun intérêt pour un réseau aussi modeste.

La fonction closeness appliquée à un graphe non connexe donne les résultats aberrants suivants :

      a          b          c          d          e          f          g 
0.03333333 0.03333333 0.02380952 0.04000000 0.02777778 0.02777778 0.02380952

Il n’est par définition pas possible de calculer une centralité de proximité pour un sommet isolé, igraph y parvient… La solution permettant d’obtenir un résultat cohérent est de décomposer le graphe puis de la mesurer sur chacune des composantes considérée individuellement. L’option mode= »all » permet d’ignorer la direction des liens.

g2 <- decompose.graph(g)
plot(g2[[1]])
sapply(g2, closeness, mode="all")
[[1]]
        a         b         c         d 
0.2500000 0.3333333 0.3333333 0.2500000 
[[2]]
e f 
1 1 
[[3]]
  g 
NaN

Le résultat est plus crédible (NaN soit Not a Number pour g effectivement). 1 pour e et f, c’est logique. Mais pourquoi 0.25 pour a et d, 0.33 pour b et c. Petit rappel : pour mesurer la closeness, on divise 1 par la somme des plus courts chemins entre un sommet et tous les autres. Pour a et d, on a donc 1/(1+1+2) ; pour b et c, 1/(1+1+1). À retenir en résumé : faire attention avec la fonction closeness qui peut facilement donner des résultats absurdes…

La fonction betweenness donne les mêmes résultats que statnet mais igraph propose également de mesurer l’intermédiarité des liens, c’est-à-dire le nombre de plus courts chemins empruntant un lien donné. Par définition, cette intermédiarité des liens sera toujours supérieure ou égale à 1 (le lien entre a et b est placé sur le plus court chemin entre ces deux points). Les résultats sont présentés dans l’ordre des liens du fichier de départ. Soit le résultat suivant (les codes des liens ont été rajoutés manuellement a posteriori) :

edge.betweenness(g, directed=TRUE) 
   ef fe  ab  ba  ac db dc bc
[1] 1  1   1  2   1  2   1   1
edge.betweenness(g, directed=FALSE)
    ef  fe  ab        ba         ac      db        dc         bc
[1] 0.5 0.5 0.8333333 0.8333333 1.3333333 1.6666667 1.3333333 1.0

Si on prend en compte l’orientation des liens, tous ont un minimum de 1 et le lien ba atteint 2 : il se situe en effet sur les plus courts chemins ba et dba.  Si on ne la prend pas en compte, les résultats sont difficiles à comprendre. Pourquoi des résultats différents pour les liens db, dc, ab et ba ? Tout simplement parce qu’il y a trop de liens pour l’opération demandée : si je ne tiens pas compte de la direction, mon graphe ne comprend que 6 liens.  Donner des résultats pour 7 oblige à des pondérations étranges… La solution la plus pertinente est de transformer d’abord le réseau en réseau non orienté (as.undirected) puis de mesurer l’intermédiarité des liens. On obtient alors un résultat cohérent :

[1] 1.5 1.5 1.0 1.5 1.5 1.0

1 correspond ici aux liens ef et bc et les quatre autres liens obtiennent un score identique. Chacun est placé sur le plus court chemin entre ses sommets terminaux et est l’un des deux plus courts chemins entre a et d : tous ont donc 1.5.

La fonction transitivity (nombre de triangles fermés parmi les voisins d’un sommet) ne permet pas de prendre en compte l’orientation des liens : il est donc nécessaire pour obtenir des résultats corrects de prendre le graphe non orienté. On obtient alors les résultats suivants

transitivity(g3, "local")
[1] 1.0000000 0.6666667 0.6666667 1.0000000       NaN       NaN       NaN

a et d sont situés à la pointe dans des triangles fermés donc la transitivité est de 1 ; efg ne font pas partie de triades donc le résultat n’est pas un nombre (NaN = Not a Numer). b et c sont voisins et tous deux liés à a et à d ; comme il manque 1 lien entre a et d, ils obtiennent un score de 2/3 (2 triangles fermés sont présents sur 3 possibles). La recherche des plus chemins, par défaut, ne tient pas compte non plus de l’orientation des liens. La fonction centralization donne des résultats corrects pour les graphes k-réguliers (score de 0) et non valides pour les graphes en étoile (voir le script où un graphe étoilé de 10 sommets obtient un score de centralisation par le degré de 0.8 seulement). En ce qui concerne les graphes bipartis, même remarque que pour statnet : igraph permet d’en définir, d’en représenter mais ne permet pas de les analyser – lorsqu’une mesure est lancée, il est considéré comme un one-mode.

Pour résumer : il est beaucoup plus facile d’importer ses données et de les filtrer avec igraph, il est aussi beaucoup plus facile d’obtenir des résultats aberrants si l’on n’est pas attentif… À venir très prochainement, un billet détaillé consacré au package bipartite.

R et statnet – revue de détail

Suite aux cours et aux TD donnés à l’ENSAI, j’ai découvert de nouveaux (et agaçants) problèmes liés à l’analyse de réseau avec R. De plus, la nécessité de commenter les résultats m’est clairement apparue. Ce billet vise donc à détailler au maximum les étapes et les résultats produits par le package statnet – un billet consacré à igraph est en cours de préparation. Le script et les fichiers de données sont téléchargeables ici.

Rappel : statnet (et igraph) permettent d’analyser des graphes non valués et one-mode. Si statnet permet de mener des modèles type ERGM, igraph est plus simple d’utilisation. Pour l’analyse de réseau biparti avec R, oubliez statnet et igraph (qui proposent pourtant des options bipartite=TRUE) et voyez plutôt du côté du package bipartite qui est, à ma connaissance, le seul à donner des résultats corrects pour ce type de réseau (voir la démonstration dans le script).

1. Liens, attributs et isolés : sur le format et l’importation des données

Le graphe fictif qui va servir d’exemple tout au long de ce billet représente les interactions autour d’une machine à café dans une petite entreprise : la population est constituée de 7 individus répartis en 2 équipes et de niveaux d’ancienneté différents. Un lien signale qu’une personne a payé un café à une autre. Le graphe est simple : pas de boucle (on ne prend pas en compte les cafés que s’offre une personne) ; pas de liens de nature différente. Il est orienté (une personne offre à une autre) et valué (combien de cafés ont été offerts par une personne à une autre dans la semaine ?).td1plot

Les sommets sont définis par deux attributs : un attribut quantitatif relatif à l’ancienneté (ANC – taille du sommet), un attribut qualitatif relatif à l’équipe (TEAM – couleur du sommet). Les liens sont définis par une direction et une intensité (INT). La valeur des attributs est donnée dans le tableau ci-dessous.

td11

Un rappel important : statnet (comme igraph) ne prend pas en compte de l’intensité des liens pour les calculs. Elle peut juste être utilisée pour la représentation graphique.

Premier problème : statnet génère un graphe à partir d’une liste de liens. Or, le sommet g est isolé. Une solution est de créer dans son fichier de départ un lien fictif g-g puis de préciser lors de l’importation qu’il n’y a pas de boucle.

L’importation des données se fait en deux temps (liens, attributs des sommets). La transformation en objet network, manipulable par statnet, nécessite de ne prendre en compte que les deux premières colonnes (origine – destination). Taper ensuite le nom du graphe permet d’en connaître les caractéristiques. De façon étrange, alors qu’il n’y a pas de boucle, statnet indique malgré tout 9 liens et non 8. Pourtant, le calcul de la densité (cf infra) donne un résultat correct – seuls les 8 liens réellement présents sont donc pris en compte dans les calculs.

library(statnet)
#import fichier
 liens <- read.table("liens.txt", header=TRUE, sep=";")
 attributs <- read.table("attributs.txt", header=TRUE, sep=";")
g <- as.network(as.matrix(liens[,-3]), loops = FALSE)
g
 Network attributes:
 vertices = 7
 directed = TRUE
 hyper = FALSE
 loops = FALSE
 multiple = FALSE
 bipartite = FALSE
 total edges= 9
 missing edges= 0
 non-missing edges= 9
Vertex attribute names:
 vertex.names
No edge attributes

Pour l’instant, les sommets ont un attribut (leur nom) et les liens n’en ont aucun. Les lignes suivantes permettent de transférer les données des data.frame vers l’objet network.

g %v% "ANC" <- attributs$ANC
g %v% "TEAM" <- as.numeric(attributs$TEAM)
g %e% "INT" <- liens$I

Si l’on tape à nouveau g, on observe que les attributs sont désormais plus nombreux.

 Vertex attribute names:
 ANC TEAM vertex.names
Edge attribute names:
 INT

Il est sans aucun doute possible de filtrer le graphe selon les attributs des liens et des sommets (par exemple, extraire le graphe des relations entre membres de l’équipe A) mais si l’opération est facile avec igraph, elle ne l’est pas avec statnet (i.e. je n’ai pas trouvé comment faire, si ce n’est en filtrant les données en amont, avant la transformation en graphe…).

Pour obtenir la figure ci-dessus, deux options sont possibles : appeler les données des data.frame, appeler les attributs de l’objet network. Le résultat est heureusement le même.

#à partir des fichiers de départ
gplot(g, gmode="digraph", displaylabels=TRUE, vertex.col=nodeColors, 
      vertex.cex= log(attributs$ANC), edge.lwd = liens$I*2)

#à partir des attributs de l'objet network
gplot(g, gmode="digraph", displaylabels=TRUE, vertex.col=nodeColor, 
      vertex.cex= log(g %v% "ANC"), edge.lwd = g %e% "INT")

2. Quelques mesures et quelques précisions

Le calcul de la densité (fonction gden) donne comme résultat 0.1904762, ce qui correspond bien à 8 (nombre de liens présents) divisé par 7*6 (nombre de liens possibles) – pour mémoire, dans un graphe simple orienté de n sommets, le nombre maximum de liens est égal à n*(n-1).

Le nombre de composantes connexes s’obtient avec la fonction components et donne ici comme résultat 5. Pourquoi 5 ? Par défaut, statnet considère que, dans un graphe orienté, la connexité recherchée est la forte : il doit exister un chemin (une suite de liens orientés) entre toute paire de sommets au sein d’une composante connexe. La composition des 5 composantes connexes est obtenue avec la commande component.dist. Si l’on souhaite les composantes faiblement connexes (au nombre de 3), il suffit de modifier l’option.

components(g)  #   5 composantes fortement connexes
cs <- component.dist(g) 
cs$membership #  et leur composition

components(g, connected="weak") #  3 composantes faiblement connexes
cw <- component.dist(g,connected="weak")
cw$membership #composition
cw$csize      #et taille

La liste des plus chemins – retour de la commande geodist – comprend deux matrices : $counts donne le nombre de plus courts chemins entre paires de sommets (il peut très bien y avoir plusieurs plus courts chemins entre deux sommets) ; $gdist donne la longueur en liens des plus courts chemins. La commande dyad.census donne un résultat facilement interprétable (nombre de liens mutuels, asymétriques et nuls). La commande triad.census donne en sortie le code MAN (mutuel, asymétrique, nul) des triades et le nombre de triades respectives. Dans le graphe fictif étudié, il n’y a aucune triade complète, et donc 0 sous le code 300.

Il est déconseillé d’utiliser la commande clique.census : même avec un graphe aussi petit, et sur un ordinateur récent et plutôt puissant, RStudio plante…

En ce qui concerne le degré, la commande degree fournit par défaut la somme des degrés entrants et sortants par sommet. Si l’on choisit de normaliser, la somme des degrés des sommets sera égale à 1 (le mode de normalisation est différent dans igraph). Le graphe étant non connexe, la commande closeness donne un résultat égal à zéro pour tous les sommets (igraph donne lui des résultats aberrants). La centralité d’intermédiarité (betweenness) donne ici 0 pour tous les sommets sauf pour b qui obtient 1 : il y a en effet un seul plus court chemin (dba) passant par b. Si l’on ne prend pas en compte la direction des liens, tous les autres sommets restent à zéro et b descend à 0.5. Pourquoi ? Il y a deux plus courts chemins entre d et a (dba et dca), et 1 sur 2 passe par b. Que c reste à zéro est par contre difficilement explicable… (si vous avez une idée, n’hésitez pas à la soumettre).

Enfin, et je termine ici pour l’examen détaillé de certaines fonctions de statnet, la mesure de la centralisation du graphe est correcte dans statnet – on obtient bien 0 avec un graphe type cercle et 1 avec un graphe étoilé – mais elle donne des résultats erronés dans igraph

Dernier point qui concerne les graphes bipartis : statnet permet (difficilement) de les importer puis de calculer des indicateurs mais ceux-ci sont faux. Dans l’exemple du script, nous avons 7 liens, 3 événements et 4 acteurs. Si les 4 acteurs participent aux 3 événements, le graphe est complet, la densité de 1 et le nombre de liens égal à 12. Or, la commande gden donne comme résultat 0.33, soit 7*2 / 7*6 – dans un graphe simple non orienté de n sommets, la densité est égale à 2n / n(n-1). Et si vous tentez l’expérience avec igraph, le résultat est le même. Le seul – et très relatif – intérêt est de pouvoir passer ensuite à des matrices acteurs-acteurs ou événements-événements. Cette dernière opération est possible sans ces packages en multipliant la matrice m par sa transposée pour obtenir la matrice acteurs-acteurs

m %*% t(m)

ou en faisant l’opération inverse (matrice événements-événements)

t(m) %*% m

Deux ateliers R : deux présentations

Deux ateliers méthodologiques sur R ont eu lieu à Toulouse en Mai dernier. La première séance était une séance d’initiation, de discussion et de présentation autour de R tandis que la seconde portait sur l’utilisation de R pour l’analyse de réseau.

Ci-dessous, nous mettons à disposition les supports qui ont alimenté la discussion lors de ces rencontres en petits comités. Un peu de temps a passé depuis ces ateliers mais le contenu est toujours d’actualité (sauf le lien pour télécharger les documents). Il est à noter que le nombre de packages CRAN a bien évolué depuis, passant de 5531 au 12 mai 2014 à 5894 au 17 septembre 2014!

Le premier support donne un aperçu d’ensemble de R: R comme langage interprété, comme système d’exploration interactif,  comme environnement de programmation.

Le second est une présentation sommaire des différents packages permettant de faire de l’analyse de réseau avec R.

Ces ateliers animés avec Sébastien Plutniak ont été soutenus par le Labex Structuration des Mondes Sociaux (SMS) et organisés par le groupe ResTo (Réseaux Sociaux à Toulouse). Merci à tous de m’avoir permis de diffuser ce contenu en ligne et merci à Pauline pour son regard acéré.