R
et d3.js
Présentateur: Arthur Katossky
Twitter: @Akatossky
Durée: 1h
R
comme système d'information géographiqueR
d3.js
pour l'interactivitéReprésenter une information, c'est avant tout choisir un canal de représentaiton qui permette de faire une correspondance la plus immédiate et la moins biaisée possible entre la donnée et la perception.
Pour résumer, on aurait la hiérarchie suivante:
La pente, l'inclinaison et les angles ont une efficacité variable (du même niveau que les longueurs à pire que la superficie) et les couleurs sont au mieux du même niveau de précision que la superficie. Le volume arrive en dernier.
On peut utiliser plusieurs canaux à la fois, et il peut y avoir redondance ou interférence.
Source: Tamara Munzner (2014, lien).
Or avec les cartes, position et longueurs sont monopolisées par le positionnement géographique.
Il ne reste donc plus à disposition pour encoder une nouvelle information que les canaux de représentation les moins efficaces: couleur par exemple.
Ne pas faire de carte.
Ex: carte électorale qui donnent principalement à voir le vote des campagnes, alors que l'essentiel des votants est concentré dans les villes.
Ex: carte des bars, carte des médecins
Contre-ex: carte des sites miniers, carte des sites touristiques
Diviser par la population de l'unité géographie. Par exemple de représenter un nombre de bar par habitant, ou un nombre de médecins par habitant.
Ex: carte des cancers dans les counties américains
La variation purement statistique peut être importante dans de petites unités géographiques ou pour des phénomènes rares. Il est alors normal qu'une proportion varie grandement uniquement à cause d'effets aléatoires.
Il faut se douter de quelques choses lorsque à la fois les taux les plus élevés et les plus faibles se situent dans la même zone de la carte.
Lisser les données ou aggréger les zones de faible taille.
R
comme système d'information géographiqueUne note sur le format raster, que nous ne verrons pas.
geojson
sf
Stocker des données géographiques est un problème non trivial.
Les objets spatiaux peuvent être assimilés à des points (ex: une source d'eau), des lignes (un cours d'eau) ou des polygones (une étendue d'eau). Mais des constructions plus complexes sont possibles!
Ex: Le delta de l'Amazone. Un pays et sa capitale. Une enclave dans un autre pays. Plusieurs pays adjacents.
On parle généralement de "géométries" pour désigner ces objets.
Des donnnées doivent également être associées à chacune des géométries.
Ex: nom / identifiant de l'objet géographique, profondeur du puit, débit du cours d'eau, type d'étendue d'eau (naturelle / artificielle)
De plus, les géométries doivent être descrit au moins dans dimensions à la surface de la Terre. (3 dimensions avec l'altitude) Or la Terre n'est que très approximativement sphérique. D'où l'existence de multiples…
systèmes de coordonnées géographiques
… ou CRS (pour coordinate reference systems, Wikipedia).
Exemple 1: le format GeoJSON
{
"type": "Feature",
"geometry": {
"type": "Point",
"coordinates": [125.6, 10.1]
},
"properties": {
"name": "Dinagat Islands"
}
}
GeoJSON
utilise le World Geodetic System 1984 (WGS 84), le système de coordonnées géographique le plus répendu, identifié 4326
par l'EPSG. (Official website, Wikipedia)
Exemple 2: le format TopoJSON
(implémenté en JavaScript par la bibliothèque topojson
de Mike Bostock, l'auteur de d3.js
)
TopoJSON
évite d'encoder deux fois les coordonnées de points ou de lignes appartenant à plusieurs géométries. (Official website, Wikipedia)
Exemple 3: le format Simple Features (implémenté en R par la bibliothèque sf
)
Dans R, la structuration est inversée: au lieu que les données soient une propriété d'une géométrie, comme en GeoJSON
, c'est la géométrie qui est une propriété d'une unité d'observtion, ligne d'un data.frame
ou d'un tibble
. (Wikipédia, site officiel)
R
Procédure générale:
(*) Certains formats utilisent concouramment plusieurs CRS mais n'identifie pas toujours au sein du fichier quel CRS est utilisé!
Exemple filé: premier tour des élections présidentielles de
(*) La géographie des bureaux de votes est publiée à la discrétion des communes ou des départements.
library(tidyverse)
votes_colonnes <-
"Code du département;Libellé du département;Code de la circonscription;Libellé de la circonscription;Code de la commune;Libellé de la commune;Code du b.vote;Inscrits;Abstentions;% Abs/Ins;Votants;% Vot/Ins;Blancs;% Blancs/Ins;% Blancs/Vot;Nuls;% Nuls/Ins;% Nuls/Vot;Exprimés;% Exp/Ins;% Exp/Vot;N°Panneau;Sexe;Nom;Prénom;Voix;% Voix/Ins;% Voix/Exp\n" %>%
read_csv2() %>% colnames
unrepeated_colnames <- votes_colonnes[-(22:28)]
repeated_colnames <- votes_colonnes[22:28]
votes_colonnes <- c(
unrepeated_colnames,
str_c( rep(repeated_colnames, times=10), "_", rep(1:10, each=7)) )
votes <- read_csv2(
"~/Données/PR17_BVot_T1_FE.txt",
skip=1,
col_types = cols(.default = "c"),
col_names = votes_colonnes,
locale=locale(encoding='latin1')
) %>%
pivot_longer(
cols = `N°Panneau_1`:`% Voix/Exp_10`,
names_to = c(".value", "Compteur"),
names_sep = "_"
) %>%
select(-Compteur) %>%
pivot_wider(
names_from = c("Prénom", "Nom"),
values_from = repeated_colnames
) %>%
mutate(
INSEE_COM=str_c(`Code du département`, `Code de la commune`)
)
library(sf)
# si la source contient de multiple couches de données géographiques
st_layers("<chemin vers dossier dézippé>/ADMIN-EXPRESS_2-1__SHP__FRA_2019-09-16/ADMIN-EXPRESS/1_DONNEES_LIVRAISON_2019-09-16/ADE_2-1_SHP_WGS84G_FRA")
Driver: ESRI Shapefile
Available layers:
layer_name geometry_type features fields
1 COMMUNE Polygon 35287 11
2 COMMUNE_CARTO Polygon 35287 11
3 ARRONDISSEMENT_DEPARTEMENTAL Polygon 321 4
4 REGION Polygon 13 3
5 REGION_CARTO Polygon 13 3
6 CHEF_LIEU Point 35287 4
7 ARRONDISSEMENT_DEPARTEMENTAL_CARTO Polygon 321 4
8 DEPARTEMENT_CARTO Polygon 96 4
9 EPCI Polygon 1244 4
10 DEPARTEMENT Polygon 96 4
11 EPCI_CARTO Polygon 1244 4
communes <- read_sf("<chemin vers dossier dézippé>/ADMIN-EXPRESS_2-1__SHP__FRA_2019-09-16/ADMIN-EXPRESS/1_DONNEES_LIVRAISON_2019-09-16/ADE_2-1_SHP_WGS84G_FRA", layer="COMMUNE")
communes
Simple feature collection with 35287 features and 11 fields
geometry type: MULTIPOLYGON
dimension: XY
bbox: xmin: 99038 ymin: 6046556 xmax: 1242436 ymax: 7110480
epsg (SRID): NA
proj4string: +proj=lcc +lat_1=44 +lat_2=49 +lat_0=46.5 +lon_0=3 +x_0=700000 +y_0=6600000 +ellps=GRS80 +units=m +no_defs
# A tibble: 35,287 x 12
ID STATUT INSEE_COM NOM_COM INSEE_ARR NOM_DEP INSEE_DEP NOM_REG INSEE_REG CODE_EPCI NOM_COM_M
<chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr> <chr>
1 BDCS… Commu… 54166 Dommar… 4 MEURTH… 54 GRAND … 44 200070738 DOMMARTI…
2 BDCS… Sous-… 54099 Val de… 1 MEURTH… 54 GRAND … 44 200070845 VAL DE B…
3 BDCS… Commu… 55270 Lahayv… 2 MEUSE 55 GRAND … 44 200034874 LAHAYVIL…
4 BDCS… Commu… 60196 La Dre… 1 OISE 60 HAUTS-… 32 246000582 LA DRENNE
5 BDCS… Commu… 72054 Champa… 2 SARTHE 72 PAYS D… 52 247200132 CHAMPAGNE
6 BDCS… Commu… 44005 Chaume… 3 LOIRE-… 44 PAYS D… 52 200067346 CHAUMES-…
7 BDCS… Commu… 49200 Longue… 1 MAINE-… 49 PAYS D… 52 244900015 LONGUENE…
8 BDCS… Commu… 61483 Bagnol… 1 ORNE 61 NORMAN… 28 200068443 BAGNOLES…
9 BDCS… Commu… 27198 Mesnil… 2 EURE 27 NORMAN… 28 200066462 MESNILS-…
10 BDCS… Commu… 27157 Marbois 2 EURE 27 NORMAN… 28 200066462 MARBOIS
# … with 35,277 more rows, and 1 more variable: geometry <MULTIPOLYGON [m]>
ggplot(communes) + geom_sf()
carte <- communes %>%
filter(INSEE_DEP=="01") %>% # moins de communes
st_simplify(dTolerance = 10) %>% # tracé moins précis
ggplot() +
geom_sf()
Rendre la carte plus propre
Ain <- st_union(communes %>% filter(INSEE_DEP=="01"))
communes %>%
filter(INSEE_DEP=="01") %>%
st_simplify(dTolerance = 10) %>%
ggplot() +
ggtitle("Les communes de l'Ain (01)") + # donner un titre
geom_sf(color='white', size=0.2) + # frontières internes plus fines
geom_sf(data=Ain, color="grey50", fill=NA) + # frontières externes plus fortes
geom_sf_text(aes(label=ifelse(STATUT!="Commune simple", NOM_COM, NA))) +
# rajouter quelques noms de commune
theme_minimal() + # pas de fond, pas d'axes, etc.
theme(
axis.text.x = element_blank(),
axis.text.y = element_blank(),
axis.title.x = element_blank(),
axis.title.y = element_blank()
)
Les projections
La projection par défaut est la projection Mercator, qui ne conserve pas fidèlement les surfaces.
Mais toutes les projections ont leur défaut. En effet, il est impossible de projeter une sphère sur un plan en conservant simmultanément les angles et les surfaces.
Ajouter les données d'intérêt aux unités géographiques:
votes_melenchon <- left_join(
by="INSEE_COM",
communes %>%
filter(INSEE_DEP=="01") %>%
st_simplify(dTolerance = 10),
votes %>%
filter(`Code du département`=="01") %>%
transmute(
INSEE_COM,
Mélenchon = as.integer(`Voix_Jean-Luc_MÉLENCHON`),
Inscrits = as.integer(Inscrits)
) %>%
group_by(INSEE_COM) %>%
summarize_all(sum)
)
votes_melenchon %>%
ggplot() +
geom_sf()
Annotations
Facettes
gganimate()
La bibliothèque sf
peut remplacer quasiment intégralement un logiciel de SIG tel que Q-GIS.
d3.js
pour l'interactivitéd3.js est une librairie JavaScript appliquée au traitement d'objets SVG
Les objets SVG s'intègrent dans l'écosystème web, aux côtés d'autres langages:
Les éléments qui nous intéressent:
p
ou des divisions div
par exemple), qu'on peut ranger dans des des classes (les paragraphes p.petits
par exemple) et des identifiants (la division div#content
par exemple)Exemple de SVG
<svg id="graphic">
<circle cx="50%" cy="90%" r="20" fill="blue"/>
<circle cx="100" cy= "40" r="10" stroke="orange" stroke-width="4"/> <circle cx="150" cy="100" r="40" stroke="purple" stroke-width="10" /> <circle cx="110" cy="200" r="90" opacity="0.5" />
<circle cx= "50" cy= "80" r="20"/>
<circle cx="350" cy="300" r="30"/>
<g>
<line x1="0" y1="0" x2="1000" y2="1000" style="stroke:red;stroke-width:2" />
<text x="20" y="-5" fill="red" transform="rotate(45)">Une tendance claire</text>
</g>
</svg>
En pratique, nous voulons utiliser les objets SVG (cercles, lignes…) et leurs propritétés (position, inclinaison, couleur…). Nous devons donc constuire ces SVG programmatiquement.
Imaginons que:
#content
communes
de type liste, où chaque entrée correspond à une commune de Francetoggle_circle
ajoute/enlève la classe ".selected" quand appliquée à un objet HTMLNous pourrions alors écrire:
let svg = d3.select("#content")
.append("svg");
for(c of communes){svg.append("g")}; // j'ajoute un élément <g></g> pour chaque commune
let groups = svg.selectAll("g");
let circles = groups
.append("circle")
.attr("r", 10)
.attr("cx", (d,i) => communes[i].x) // ignorons le "d" pour l'instant
.attr("cy", (d,i) => communes[i].y)
.attr("stroke", (d,i) => communes[i].color);
circles.on("click", toggle_circle);
Ce qui en d3.js
plus classique donnerait maintenant:
let svg = d3.select("#content")
.append("svg");
let groups = svg
.selectAll("g") // 1. Il n'existe aucune balise <g> à ce stade.
.data(communes) // 2. L'objet "communes" est lié à la sélection
.join("g"); // 3. Une balise <g> est ajoutée par objet dans `countries`.
let circles = groups
.append("circle")
.attr("r", 10)
.attr("cx", d => d.x) // plus besoin de "communes" ou de "i"
.attr("cy", d => d.y)
.attr("stroke", d => d.color);
circles.on("click", toggle_circle);
Présentateur: Arthur Katossky
Twitter: @Akatossky