Comment enrichir des données ouvertes avec les API publiques ?
Ce projet explore le jeu de données Arbres répertoriés de la Ville de Québec, l’enrichit via l’API du GBIF (Global Biodiversity Information Facility) et le croise avec les shapefiles des quartiers et arrondissements. Tu verras comment ajouter des noms latins, des médias et des informations géospatiales à un fichier CSV brut, puis produire des cartes de densité et des visualisations par quartier avec R.
C’était en janvier 2016. Je venais de tomber sur le catalogue de données ouvertes de la Ville de Québec et je me suis dit : et si on poussait l’exercice plus loin que la simple extraction ?
Les données sont sous licence Creative Commons Attribution 4.0. Je me suis donné un objectif : tirer le maximum d’information du jeu de données Arbres répertoriés.
Le projet complet et la compilation originale sont sur RPubs. J’ai présenté ces travaux le 5 mars 2016 à la JIDO2016, au CAMP.
Tout le code source du projet est dans le dossier d’accompagnement : static/images/blogue/2016/2016-03-04-foret-donnees-arbres-quebec/.
🌘 Chargement du jeu de données
Le fichier source ARBRE.csv est un CSV séparé par des | avec une virgule comme séparateur décimal. Les dates sont stockées dans un format numérique YYYYMMDDHHMMSS qu’il faut interpréter.
dA <- read.csv("ARBRE.csv", sep = "|", dec = ",", stringsAsFactors = FALSE) %>%
mutate(DATE_PLANTE = parse_date_time(as.character(DATE_PLANTE / 1000000),
orders = "%Y%m%d"))
Le jeu de données contient 29 140 observations de 15 variables.
🌘 Classe de chaque variable
Voici la classe attribuée par R à chaque colonne :
| Variable | Classe |
|---|---|
| CODE_ARR | character |
| CODE_PARC | character |
| EMPLACEMENT | character |
| NOM_FRAN | character |
| NOM_LAT | character |
| TYPE_ARBRE | character |
| POS_MESURE | character |
| CIBLE | character |
| DATE_PLANTE | POSIXlt |
| DIAMETRE | numeric |
| LONGITUDE | numeric |
| LATITUDE | numeric |
| OBJECTID | integer |
| ID_VILLE | integer |
| QUARTIER | integer |
🌘 Exploration de base
Les dix valeurs les plus fréquentes pour chaque variable qualitative donnent un bon aperçu. Par exemple, l’érable de Norvège (Acer platanoides) domine largement le lot, suivi du frêne de Pennsylvanie et du peuplier deltoïde.
Côté quantitatif, le diamètre moyen des arbres est d’environ 23 cm, avec des valeurs minimales et maximales qui suggèrent quelques erreurs de saisie (certaines valeurs semblent inscrites en millimètres plutôt qu’en centimètres).
dA %>% summarise(
moyDIA = mean(DIAMETRE, na.rm = TRUE),
minDIA = min(DIAMETRE, na.rm = TRUE),
maxDIA = max(DIAMETRE, na.rm = TRUE),
minLON = min(LONGITUDE, na.rm = TRUE),
minLAT = min(LATITUDE, na.rm = TRUE),
maxLON = max(LONGITUDE, na.rm = TRUE),
maxLAT = max(LATITUDE, na.rm = TRUE)
)
🌘 Distribution des diamètres
range <- quantile(dA$DIAMETRE, probs = c(0.02, 0.98), na.rm = TRUE)
ggplot(
data = dA %>% filter(range[1] <= DIAMETRE & DIAMETRE < range[2] & POS_MESURE != ""),
mapping = aes(x = DIAMETRE, fill = POS_MESURE)
) + geom_histogram() + facet_wrap("POS_MESURE")

🌘 Distribution des dates de plantation
ggplot(
data = dA %>% filter(TYPE_ARBRE != "NON DISPONIBLE"),
mapping = aes(x = DATE_PLANTE, fill = TYPE_ARBRE)
) + geom_histogram() + facet_wrap("TYPE_ARBRE")

🌘 Enrichissement des variétés d’arbres avec l’API GBIF
Le Système mondial d’information sur la biodiversité (GBIF) offre une API publique pour retrouver les noms scientifiques, les classifications, les médias et la répartition des espèces. C’est l’occasion parfaite pour enrichir notre jeu de données.
🌘 Construction des requêtes
Chaque nom d’arbre est transformé en URL compatible avec l’API species/match du GBIF.
get_url_gfib <- function(x)
httr::GET(url = paste0("http://api.gbif.org/v1/species/match/?name=", x))
build_name <- function(x)
gsub(pattern = " ",
replacement = "+",
x %>%
strsplit("'") %>%
unlist %>%
'['(1) %>%
trimws())
nomsUniques <-
dA %>%
select(NOM_LAT) %>%
distinct() %>%
mutate(nom_url = sapply(NOM_LAT, build_name) %>% tolower())
nomsUrlUniques <-
nomsUniques %>%
select(nom_url) %>%
distinct()
🌘 Transformation des résultats JSON
Les réponses JSON sont converties en une table qu’on peut joindre aux données sources.
load("data_json_gfib.RData")
json_content <- sapply(data_json_gfib %>%
as.data.frame %>%
'$'(content), rawToChar)
json_content2 <- data.frame(
nom_url = names(json_content),
json_content,
row.names = NULL,
stringsAsFactors = FALSE
)
json_content3 <- json_content2$json_content %>%
lapply(fromJSON, flatten = TRUE) %>%
lapply(as.data.frame) %>%
(function(x) do.call(smartbind, x)) %>%
cbind(nom_url = json_content2$nom_url)
json_content4 <- merge(json_content3, nomsUniques, by = c("nom_url"))
🌘 Jointure avec les données source
dA2 <- dA %>% partition() %>% merge(json_content4, by = c("NOM_LAT")) %>% collect()
🌘 Médias par espèce
Une deuxième API permet de récupérer les photos associées à chaque espèce.
get_url_media <- function(x)
httr::GET(url = paste0("http://api.gbif.org/v1/species/", x, "/media"))
disct_speciesKey <- dA2 %>% select(speciesKey) %>% filter(!is.na(speciesKey)) %>% distinct()
load("json_media.RData")
json_media1 <- data.frame(
json_content = sapply(json_media %>%
as.data.frame %>%
'$'(content), rawToChar),
stringsAsFactors = FALSE
) %>%
mutate(json_content1 = json_content %>%
lapply(fromJSON, flatten = TRUE) %>%
sapply('[', "results"))
json_media1$speciesKey <- disct_speciesKey
json_media2 <- json_media1[lapply(json_media1$json_content1, class) == 'data.frame', ]
json_media3 <- json_media2 %>%
'$'(json_content1) %>%
sapply(as.data.frame) %>%
reshape2::melt() %>%
select(value, type, format, identifier, references, title,
description, source, creator, publisher, license) %>%
distinct(value)
json_media4 <- cbind(json_media3, json_media2$speciesKey)
🌘 Jointure avec les données médias
dA3 <- merge(dA2, json_media4, all.x = TRUE)
🌘 Ajout du quartier et de l’arrondissement
Les shapefiles des quartiers et arrondissements (QUARTIERS/ et ARROND/ dans le dossier du projet) sont chargés avec rgdal, transformés en coordonnées WGS84, puis les points des arbres y sont superposés.
qrtqc <- readOGR("QUARTIERS/", layer = "QUARTIER") %>%
spTransform(CRS("+proj=longlat +datum=WGS84"))
arrqc <- readOGR("ARROND/", layer = "ARROND") %>%
spTransform(CRS("+proj=longlat +datum=WGS84"))
names(qrtqc@data) <- paste0(names(qrtqc@data), "_QRT")
names(arrqc@data) <- paste0(names(arrqc@data), "_ARR")
coordinates(dA3) <- ~ LONGITUDE + LATITUDE
proj4string(dA3) <- CRS("+proj=longlat +datum=WGS84")
dA4.1 <- dA3 %>% over(qrtqc) %>% cbind(dA3@data)
dA4 <- dA3 %>% over(arrqc) %>% cbind(dA4.1) %>% cbind(dA3@coords)
save(dA4, file = "dA4.RData")
readr::write_csv(dA4, "arbres-augmented.csv")
Le fichier arbres-augmented.csv contient maintenant les données originales enrichies des informations taxonomiques, des médias et des divisions administratives.
🌘 Arbre le plus courant par quartier (avec photo)
| NOM_QRT | scientificName | freq |
|---|---|---|
| Chute-Montmorency | Acer platanoides | 30 |
| Fargy—Du Parc | Acer platanoides | 92 |
| … | … | … |
Pas de surprise : l’érable de Norvège est l’espèce la plus représentée dans presque tous les quartiers.
🌘 Cartes de densité
🌘 Fond de carte OpenStreetMap
select_for_map <- dA4 %>% select(LONGITUDE, LATITUDE, order)
range_long <- range(select_for_map$LONGITUDE)
range_lat <- range(select_for_map$LATITUDE)
load("quebec_map.RData")
QuebecMap <- ggmap(quebec_map, base_layer = ggplot(aes(x = LONGITUDE, y = LATITUDE),
data = select_for_map))
map1 <- QuebecMap + scale_fill_gradient(low = "blue", high = "red")
🌘 Carte simple de densité
map1 +
stat_density2d(aes(x = LONGITUDE, y = LATITUDE, fill = ..level..),
geom = "polygon", data = select_for_map)

🌘 Carte composée par ordre botanique
table_order <- with(select_for_map, table(order))
select_for_map2 <- table_order %>%
as.data.frame() %>%
merge(select_for_map, all.y = TRUE) %>%
filter(Freq > 500)
select_for_map2$order2 <- factor(select_for_map2$order)
map1 +
stat_density2d(aes(x = LONGITUDE, y = LATITUDE, fill = ..level..),
geom = "polygon", data = select_for_map2) +
facet_wrap(facets = "order2")

🌘 Arbres recensés par quartier et arrondissement
Source de l’approche : plotting polygon shapefiles with ggplot2
qrtqc@data$id <- rownames(qrtqc@data)
qrtqc.df <- as.data.frame(qrtqc)
qrtqc.fort <- fortify(qrtqc, region = "id")
qrtqc.line <- join(qrtqc.fort, qrtqc.df, by = "id")
arrqc@data$id <- rownames(arrqc@data)
arrqc.df <- as.data.frame(arrqc)
arrqc.fort <- fortify(arrqc, region = "id")
arrqc.line <- join(arrqc.fort, arrqc.df, by = "id")
ggmap_quartiers <- ggplot(qrtqc.line) +
aes(long, lat, group = group, fill = NOM_QRT) +
geom_polygon() +
geom_path(color = "white") +
coord_equal()
ggmap_arrond <- ggplot(arrqc.line) +
aes(long, lat, group = group, fill = NOM_ARR) +
geom_polygon() +
geom_path(color = "white") +
coord_equal()
plot_data_quartiers <-
ddply(dA4, .(NOM_QRT, order), summarise, freq = length(NOM_QRT)) %>%
filter(!is.na(order) & freq >= 500)
plot_data_arrond <-
ddply(dA4, .(NOM_ARR, order), summarise, freq = length(NOM_QRT)) %>%
filter(!is.na(order) & freq >= 500)
gg_freq_ordre_quartier <- ggplot(data = plot_data_quartiers,
aes(x = order, y = freq, fill = order)) +
geom_bar(position = "stack", stat = "identity") +
facet_wrap(facets = "NOM_QRT", ncol = 4) +
scale_x_discrete(breaks = order, labels = NULL) +
xlab("Ordre") + ylab("Fréquence") +
ggtitle("Ordre par quartier")
gg_freq_ordre_arrond <- ggplot(data = plot_data_arrond,
aes(x = order, y = freq, fill = order)) +
geom_bar(position = "stack", stat = "identity") +
facet_wrap(facets = "NOM_ARR") +
scale_x_discrete(breaks = order, labels = NULL) +
xlab("Ordre") + ylab("Fréquence") +
ggtitle("Ordre par arrondissement")
# Carte des quartiers
ggmap_quartiers

# Fréquence par quartier
gg_freq_ordre_quartier

# Carte des arrondissements
ggmap_arrond

# Fréquence par arrondissement
gg_freq_ordre_arrond

🌘 Ce que tu retiendras
Un fichier CSV tout simple — des arbres sur le territoire de Québec — peut devenir un objet d’analyse riche dès qu’on le croise avec des données externes. L’API du GBIF ajoute une couche taxonomique mondiale, les shapefiles locaux ancrent les données dans le territoire, et quelques lignes de ggplot2 suffisent pour révéler des motifs cachés dans la répartition des espèces.
Les fichiers sources sont dans le dossier d’accompagnement : static/images/blogue/2016/2016-03-04-foret-donnees-arbres-quebec/. Tu y trouveras les données originales, les shapefiles, les fichiers RData pré-téléchargés et le script R complet.
🌘 Version Quarto
Le document original a été converti au format Quarto moderne :
Va, explore, enrichis.