Comment visualiser les attaques de piraterie maritime avec R et Leaflet ?
Le jeu de données Crime at Sea (1993-2020) est importé dans R, converti en objets géospatiaux sf, exploré avec tidyverse, puis cartographié avec Leaflet. Des icônes personnalisées et une application Shiny interactive complètent l’analyse.
Je suis tombé sur un jeu de données fascinant : les attaques de pirates contre la marine marchande, compilées bénévolement sur une période de près de 30 ans. J’ai voulu voir à quoi ça ressemblait une fois cartographié.
Le projet complet est dans le dépôt Maritime-Pirate-Attacks et contient les données “Crime at Sea: A Global Database of Maritime Pirate Attacks (1993–2020)”.
Tu trouveras tout le code source de cet article sur mon dépôt Git.
🌘 Import et préparation des données
On commence par lire le fichier CSV avec read_csv et on le convertit en objet géospatial avec la librairie sf.
pirate_attacks_sf <-
read_csv(
"data/csv/pirate_attacks.csv",
col_types = cols(
date = col_date(format = ""),
time = col_character(),
longitude = col_double(),
latitude = col_double(),
attack_type = col_character(),
location_description = col_character(),
nearest_country = col_character(),
eez_country = col_character(),
shore_distance = col_double(),
shore_longitude = col_double(),
shore_latitude = col_double(),
attack_description = col_character(),
vessel_name = col_character(),
vessel_type = col_character(),
vessel_status = col_character(),
data_source = col_character()
)
) %>%
st_as_sf(coords = c("longitude", "latitude"))
La fonction st_as_sf transforme les colonnes de longitude/latitude en une colonne geometry de type POINT. C’est le format standard sf (Simple Features) qui permet de manipuler des données géospatiales dans R.
🌘 Exploration des données
Je sélectionne un sous-ensemble plus propre pour l’analyse : je filtre sur la source imb (la plus récente et la mieux renseignée), je remplace les valeurs manquantes du statut par “Underway”, et j’extrais le mois et l’année.
pirate_attacks_sf_ss <- pirate_attacks_sf %>%
filter(data_source == "imb") %>%
select(
date,
attack_type,
shore_distance,
vessel_type,
vessel_status
) %>%
replace_na(list(vessel_status = "Underway")) %>%
mutate(month_event = month(date),
year_event = year(date))
J’utilise la librairie dataReporter pour générer un rapport descriptif automatique qui m’aide à repérer les anomalies, les valeurs manquantes et la distribution des variables.
makeDataReport(pirate_attacks_sf_ss, replace = TRUE)
🌘 Catégorisation des types de navires
La variable vessel_type contient beaucoup de variantes. Je regroupe manuellement ces types en catégories plus larges. J’exporte d’abord la liste des types uniques.
pirate_attacks_sf_ss %>%
group_by(vessel_type) %>%
st_set_geometry(NULL) %>%
count() %>%
arrange(desc(n)) %>%
write_csv("vessel_types.csv")
Puis je crée une table de correspondance que je réimporte :
custom_vessel_categories <-
read_csv(
"data/csv/custom_vessel_categories.csv",
col_types = cols(vessel_type = col_character(),
vessel_category = col_character())
)
pirate_attacks_sf_ss2 <- pirate_attacks_sf_ss %>%
left_join(custom_vessel_categories)
Enfin, j’exporte le tout en GeoJSON pour le réutiliser dans l’application Shiny.
pirate_attacks_sf_ss2 %>%
sf::write_sf("data/geojson/pirate_attacks_sf_ss2.geojson")
🌘 Icônes personnalisées
Pour rendre la carte plus parlante, j’ai créé un icône de pirate depuis FlatIcons avec Gimp, ainsi qu’une ombre portée.


On les assemble dans R avec makeIcon :
pirateIcon <- makeIcon(
iconUrl = "pirate.png",
iconWidth = 32,
iconHeight = 32,
iconAnchorX = 16,
iconAnchorY = 31,
shadowUrl = "pirate-ombre.png",
shadowWidth = 64,
shadowHeight = 64,
shadowAnchorX = 32,
shadowAnchorY = 62
)
🌘 Carte Leaflet interactive
Je filtre par exemple sur janvier 2015 pour tester la visualisation.
pirate_attacks_sf_maps <- pirate_attacks_sf_ss2 %>%
filter(year_event == 2015, month_event == 1)
Je construis la carte Leaflet avec addTiles() pour le fond OpenStreetMap, des popups HTML détaillés, l’icône personnalisé et le regroupement des marqueurs avec markerClusterOptions.
m <- leaflet(pirate_attacks_sf_maps) %>%
addTiles() %>%
addMarkers(
popup = paste0(
"Type: ", pirate_attacks_sf_maps$vessel_category, "<br>",
"Statut: ", pirate_attacks_sf_maps$vessel_status, "<br>",
"Type d'attaque: ", pirate_attacks_sf_maps$attack_type, "<br>",
"Distance de la côte: ", round(pirate_attacks_sf_maps$shore_distance, 2), "km", "<br>",
"Date: ", pirate_attacks_sf_maps$date
),
icon = pirateIcon,
clusterOptions = markerClusterOptions()
)
Voici le résultat en capture d’écran :

🌘 Application Shiny interactive
J’ai emballé le tout dans une application Shiny qui permet de filtrer par type de navire et par période.
L’interface utilisateur (ui) contient un sélecteur de type de navire, un sélecteur de dates, et la carte Leaflet.
ui <- fluidPage(
theme = shinytheme("lumen"),
titlePanel("Attaques pirates contre des navires"),
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "vessel",
label = strong("Vessel category"),
choices = unique(pirate_attacks_sf_ss2$vessel_category),
selected = "Bulk"
),
dateRangeInput(
"date",
strong("Date range"),
start = min(pirate_attacks_sf_ss2$date),
end = max(pirate_attacks_sf_ss2$date),
min = min(pirate_attacks_sf_ss2$date),
max = max(pirate_attacks_sf_ss2$date)
),
),
mainPanel(
leafletOutput("pirateMap"),
p(),
tags$a(
href = "https://github.com/newzealandpaul/Maritime-Pirate-Attacks",
"Crime at Sea: A Global Database of Maritime Pirate Attacks (1993 - 2020)",
target = "_blank"
)
)
)
)
Le serveur (server) filtre les données de façon réactive selon les critères sélectionnés et produit la carte Leaflet.
server <- function(input, output) {
pirate_attacks_sf_maps <- reactive({
req(input$date)
req(input$vessel)
validate(need(
!is.na(input$date[1]) & !is.na(input$date[2]),
"Error: Please provide both a start and an end date."
))
validate(need(
input$date[1] < input$date[2],
"Error: Start date should be earlier than end date."
))
pirate_attacks_sf_ss2 %>%
filter(
vessel_category == input$vessel,
as.POSIXct(date) > as.POSIXct(input$date[1]) &
as.POSIXct(date) < as.POSIXct(input$date[2])
)
})
output$pirateMap <- renderLeaflet({
leaflet() %>%
addTiles() %>%
addMarkers(
data = pirate_attacks_sf_maps(),
popup = paste0(
"Type: ", pirate_attacks_sf_maps()$vessel_category, "<br>",
"Statut: ", pirate_attacks_sf_maps()$vessel_status, "<br>",
"Type d'attaque: ", pirate_attacks_sf_maps()$attack_type, "<br>",
"Distance de la côte: ", round(pirate_attacks_sf_maps()$shore_distance, 2), "km", "<br>",
"Date: ", pirate_attacks_sf_maps()$date
),
icon = pirateIcon,
clusterOptions = markerClusterOptions()
)
})
}
shinyApp(ui, server)
Tu peux tester l’application sur ShinyApps.io tant que le quota mensuel n’est pas atteint :
https://francoispelletiertest.shinyapps.io/AttaquesPiratesMaritime/
🌘 Pour aller plus loin
Ce projet est une belle démonstration de tout ce qu’on peut faire avec R, sf et leaflet pour analyser et visualiser des données géospatiales. Tu peux télécharger le jeu de données complet et l’adapter à d’autres questions : quels sont les corridors les plus dangereux ? Y a-t-il une saisonnalité des attaques ?
Le code source complet est disponible sur mon dépôt Git :
https://git.francoispelletier.org/partage/maritime-pirate-attacks
🌘 Version Quarto
Ce document a été converti au format Quarto :
Les fichiers sources sont aussi inclus dans le dossier images de cet article pour référence.