Attaques de pirates sur des navires

Je te présente un projet d’exploration de données géospatiales avec le langage R. J’ai utilisé un jeu de données compilées bénévolement. Celles-ci concernent les attaques de pirates sur les navires de la marine marchande.

Comme le sujet des chaînes d’approvisionnement est d’actualité, et que de plus, on entend parler de piratage informatique énormément, je me suis dit que j’irais voir ce qui se passe à mi-chemin. J’ai donc fait une petite enquête sur les vrais pirates des mers !

Afin de visualiser ces données, j’ai donc créé une application avec l’outil Shiny. C’est un produit développé pour le langage de programmation R qui permet de concevoir des applications web.

Exploration des données

Les données proviennent d’un répertoire GitHub partagé: https://github.com/newzealandpaul/Maritime-Pirate-Attack.

Le jeu de données est intitulé : « Crime at Sea: A Global Database of Maritime Pirate Attacks (1993–2020) ». Il contient trois fichiers, dont une table de correspondance pour les codes de pays, ainsi que des statistiques par pays et par année. Le fichier principal comporte les données des attaques de pirates, provenant de deux sources distinctes, et couvrant une période d’environ 18 ans.

Nous débutons par la lecture des données depuis le fichier pirate_attacks.csv

À des fins d’exhaustivité, j’ai inclus le formatage complet de toutes les colonnes du fichier. Lorsque l’on omet les informations dans le paramètre col_types, la fonction effectue la lecture du fichier puis pose des hypothèses.

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"))

Nous allons convertir les données en tableau géospatial avec la fonction st_as_sf de la librairie sf. La signification de sf est Simple Features, un format normalisé de données géospatiales utilisé de plus en plus avec le langage R.

Voici un aperçu des données, en utilisant la fonction glimpse().

Show in New Window
Rows: 7,511
Columns: 15
$ date                 <date> 1993-01-02, 1993-01-04, 1993-01-06,…
$ time                 <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ attack_type          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ location_description <chr> "Hong Kong - Luzon - Hainan", "Hong …
$ nearest_country      <chr> "CHN", "CHN", "CHN", "CHN", "PHL", "…
$ eez_country          <chr> "TWN", "CHN", "TWN", "CHN", "PHL", "…
$ shore_distance       <dbl> 357.5023726, 47.4315725, 280.8118709…
$ shore_longitude      <dbl> 115.825956, 115.825956, 114.302501, …
$ shore_latitude       <dbl> 22.746644, 22.746644, 22.044867, 29.…
$ attack_description   <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ vessel_name          <chr> "Mv Cosmic Leader", "Mv Tricolor Sta…
$ vessel_type          <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ vessel_status        <chr> NA, NA, NA, NA, NA, NA, NA, NA, "Anc…
$ data_source          <chr> "mappingpiracy", "mappingpiracy", "m…
$ geometry             <POINT> POINT (116.9667 19.7), POINT (116 …

Nous allons maintenant sélectionner un sous-ensemble de ces données:

  • La source de données la plus récente, soit imb
  • Les colonnes suivantes:
    • date
    • attack_type
    • shore_distance
    • vessel_type
    • vessel_status
  • Nous allons remplacer les valeurs manquantes de vessel_status par « Underway ».
  • Nous allons extraire le mois et l’année, afin de faciliter l’exploration des données sur ces périodes.

Ce qui nous donne la section de code suivante:

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))

Nous allons ensuite générer un rapport automatisé de description des données à l’aide de la librairie dataReporter.

makeDataReport(pirate_attacks_sf_ss)

Correction des types de navires

J’observe que la variable vessel_type aurait besoin d’être nettoyée, et qu’elle a plusieurs modalités avec une cardinalité inférieure à 5.

vessel_typen
Bulk Carrier322
Product Tanker252
Container119
General Cargo67
Chemical Tanker53
Tug45
Crude Oil Tanker40
LPG Tanker40
Tanker30
Fishing Vessel27

Je vais donc créer une nouvelle variable vessel_category. Pour ce faire, j’exporte les différentes valeurs de vessel_type dans un fichier CSV.

pirate_attacks_sf_ss %>%
  group_by(vessel_type) %>%
  st_set_geometry(NULL) %>%
  count() %>%
  arrange(desc(n)) %>%
  write_csv("vessel_types.csv")

Je crée une catégorisation manuelle dans la colonne vessel_category depuis un tableur. Voici un extrait de cette table.

vessel_typevessel_category
Accommodation BargeOther
Asphalt/Bitumen TankerTanker
Bulk CarrierBulk
BULK CARRIERBulk
Bunkering TankerTanker
Cable ShipOther
Cement CarrierBulk
Chemical TankerTanker
ContainerContainer
CONTAINERContainer

Je réimporte ensuite ces nouvelles catégories.

custom_vessel_categories <-
  read_csv(
    "data/csv/custom_vessel_categories.csv",
    col_types = cols(vessel_type = col_character(),
                     vessel_category = col_character())
  )

Puis, je les joints aux données précédentes sur les attaques.

pirate_attacks_sf_ss2 <- pirate_attacks_sf_ss %>% 
  left_join(custom_vessel_categories)

Enfin, je les exporte dans un fichier de données géospatiales GéoJSON afin de pouvoir les réutiliser dnas l’application Shiny.

Conception d’une carte des attaques de pirates sur des navires

Je vais maintenant construire une première carte pour me familiariser avec les données.

Je vais créer un filtre sur les dates. Je tiens à noter ici que finalement, comme dans Shiny, le sélecteur de dates fonctionne par jour, je ne retiendrai pas ce type de filtrage. Je sélectionne ici les données de janvier 2015.

pirate_attacks_sf_maps <- pirate_attacks_sf_ss2 %>%
  filter(year_event == 2015, month_event==1)

J’utilise maintenant Leaflet pour créer une carte dynamique en JavaScript. J’utilise le fond de carte par défaut qui est OpenStreetMap.

Afin d’agrémenter la visualisation, j’ai conçu un icône de pirate depuis FlatIcons, à l’aide du logiciel Gimp.

J’ai aussi créé une ombre portée pour augmenter la qualité du visuel.

Je combine les deux à l’aide de 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
)

Je crée une vignette dynamique en HTML (popup) qui contient les informations sur l’attaque. Je spécifie ensuite que je souhaite effectuer des regroupements lorsque les points sont très rapprochés.

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 de cette carte d’attaques de pirates sur des navires en janvier 2015, sous la forme d’une capture d’écran.

Une carte du monde qui contient trois point: un point simple représenté par un icône de pirate, un regroupement représenté un cercle vert contenant le nombre 4 et un autre de couleur jaune contenant le nombre 14.

Création de l’application de visualisation des attaques de pirates sur des navires

J’utilise un modèle d’application traditionnel qui contient deux éléments distincts: une interface utilisateur ui et un module de traitement des données server.

Interface utilisateur

L’interface utilisateur est composée d’un menu de sélection du type de navires, créé avec selectInput, ainsi qu’un outil de sélection de dates, dateRangeInput. Ces deux composantes sont inclues dans un panneau latéral sidebarPanel.

Le panneau principal mainPanel contient la carte, créée avec leafletOutput.

ui <- fluidPage(
  theme = shinytheme("lumen"),
  titlePanel("Attaques pirates contre des navires"),
  sidebarLayout(
    sidebarPanel(
      # Select type of vessel to plot
      selectInput(
        inputId = "vessel",
        label = strong("Vessel category"),
        choices = unique(pirate_attacks_sf_ss2$vessel_category),
        selected = "Bulk"
      ),
      # Select date range to be plotted
      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"
      )
    )
  )
)

Serveur

Le serveur contient une section réactive qui permet de filtrer les données selon le type de navire et l’intervalle de dates sélectionné.

Nous spécifions d’abord les valeurs requises, ainsi que les plages de valeurs autorisées.

Ensuite, nous filtrons les données avec la fonction filter.

  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])
      )
    
  })

Nous avons ensuite la section où nous générons la carte. Cette section est quasiment identique à celle utilisée en exploration plus haut. Sauf qu’au lieu d’utiliser pirate_attacks_sf_maps comme un jeu de données, c’est maintenant une fonction étant donné qu’il doit être recalculé à chaque changement du filtre.

Le code devient alors comme suit, et on génère le module Shiny pour Leaflet avec la fonction renderLeaflet.

output$pirateMap <- renderLeaflet({
    source("leaflet_pirate_popuptext.R")
    
    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()
      )
  })

Enfin, on exécute l’application avec cette fonction.

shinyApp(ui, server)

Utiliser l’application

L’application qui permet de consulter les attaques de pirates sur des navires est disponible sur ce site web:

https://francoispelletiertest.shinyapps.io/AttaquesPiratesMaritime/

Notez que j’utilise un hébergeur gratuit qui offre 25 heures d’exécution par mois. Donc, si l’application ne fonctionne plus, c’est qu’elle a dépassé ce quota !

Le code source ainsi que les données sont disponibles ici:

https://git.francoispelletier.org/partage/maritime-pirate-attacks

Articles connexes