Séries chronologiques de la délinquance enregistrée (SSMSI)

Données - Sécurité intérieure (SSMSI)

Auteur·rice
Code
source(here::here("_rinit.R"))
invisible(Sys.setlocale("LC_TIME", "fr_FR.UTF-8"))
# cd ~/iCloud/website/data/ssmsi; /usr/local/bin/R -e 'quarto::quarto_render("serieschrono-datagouv.qmd")'

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
                      fig.width = 9, fig.height = 5.5, dpi = 150)

sc_path <- here::here("data", "ssmsi", "serieschrono-datagouv.parquet")

# Unite_temps : "YYYY" (annuel), "YYYYMmm" (mensuel), "YYYYTq" (trimestriel).
parse_ut <- function(x) {
  y  <- as.integer(substr(x, 1, 4))
  m  <- dplyr::case_when(
    grepl("M", x) ~ as.integer(sub(".*M", "", x)),
    grepl("T", x) ~ (as.integer(sub(".*T", "", x)) - 1L) * 3L + 1L,
    TRUE          ~ 1L
  )
  as.Date(sprintf("%d-%02d-01", y, m))
}

sc <- arrow::read_parquet(sc_path) |>
  dplyr::filter(!is.na(Valeurs)) |>
  dplyr::mutate(
    date = parse_ut(Unite_temps),
    # Le champ Periodicite est vide pour ~la moitié des lignes ; on la
    # redéduit du format de Unite_temps ("YYYY" / "YYYYMmm" / "YYYYTq").
    freq = dplyr::case_when(
      grepl("M", Unite_temps) ~ "Mensuelle",
      grepl("T", Unite_temps) ~ "Trimestrielle",
      TRUE                     ~ "Annuelle"
    )
  )

n_series <- dplyr::n_distinct(sc$Titre)
n_zones  <- dplyr::n_distinct(sc$Zone_geographique)
annee    <- range(as.integer(substr(sc$Unite_temps, 1, 4)))

theme_barres <- theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold"),
        plot.subtitle = element_text(colour = "grey35"),
        panel.grid.minor = element_blank())

Recueil hétérogène de 160 séries chronologiques publiées par le SSMSI sur data.gouv.fr : nombres d’infractions, de victimes, de mis en cause et de véhicules, en niveau et pour 1 000 habitants, à des pas de temps annuel, trimestriel ou mensuel, pour la France, la France métropolitaine, les régions et les 105 zones géographiques (départements pour l’essentiel), de 2006 à 2026.

Code
sc |>
  dplyr::group_by(Série = Titre, Périodicité = freq) |>
  dplyr::summarise(
    `Zones`    = dplyr::n_distinct(Zone_geographique),
    `Début`    = min(as.integer(substr(Unite_temps, 1, 4))),
    `Fin`      = max(as.integer(substr(Unite_temps, 1, 4))),
    `Obs.`     = dplyr::n(),
    .groups = "drop"
  ) |>
  dplyr::arrange(Série) |>
  reactable::reactable(
    searchable = TRUE, filterable = TRUE, pagination = TRUE,
    defaultPageSize = 15, compact = TRUE, resizable = TRUE, highlight = TRUE, wrap = TRUE,
    defaultColDef = reactable::colDef(
      align = "left", headerStyle = list(fontWeight = "bold"),
      style = list(whiteSpace = "normal", wordBreak = "break-word")),
    columns = list(
      Série       = reactable::colDef(minWidth = 260),
      Périodicité = reactable::colDef(minWidth = 90, maxWidth = 120),
      Zones       = reactable::colDef(minWidth = 55, maxWidth = 80, align = "right"),
      Début       = reactable::colDef(minWidth = 55, maxWidth = 80, align = "right"),
      Fin         = reactable::colDef(minWidth = 55, maxWidth = 80, align = "right"),
      Obs.        = reactable::colDef(minWidth = 55, maxWidth = 90, align = "right",
                                      format = reactable::colFormat(separators = TRUE)))
  )

Séries mensuelles CVS-CJO, France

Code
mens <- sc |>
  dplyr::filter(freq == "Mensuelle", Zone_geographique == "France", grepl("CVS-CJO", Titre))
# Les 3 derniers mois sont provisoires (parfois une rupture nette de niveau) :
# on les écarte du graphique.
cutoff <- max(mens$date) - months(3)

mens |>
  dplyr::filter(date <= cutoff) |>
  dplyr::mutate(lab = sub(" enregistr.*", "", Titre) |> stringr::str_wrap(28)) |>
  ggplot(aes(date, Valeurs, colour = Correction)) +
  geom_line(linewidth = 0.5, show.legend = FALSE) +
  scale_colour_manual(values = c("#c0392b", "#3b6f8a", "#d98c00"), na.value = "#c0392b") +
  facet_wrap(~ lab, scales = "free_y", ncol = 3) +
  scale_y_continuous(labels = scales::label_number(big.mark = " ", scale_cut = scales::cut_short_scale()),
                     limits = c(0, NA)) +
  labs(title = "Faits enregistrés par mois, France (CVS-CJO)",
       subtitle = "Échelle propre à chaque panneau ; 3 derniers mois (provisoires) écartés",
       caption = "Grandeur variable selon l'indicateur : infractions, victimes, véhicules ou mis en cause.",
       x = NULL, y = NULL) +
  theme_barres +
  theme(strip.text = element_text(size = 8.5, face = "bold"),
        axis.text = element_text(size = 7),
        plot.caption = element_text(colour = "grey35"))

Cambriolages de logement — évolution annuelle par département

Code
camb_ann <- sc |>
  dplyr::filter(freq == "Annuelle",
                grepl("^Cambriolages de logements", Titre),
                Ordonnees == "Nombre d'infractions",
                grepl("^[0-9]", Zone_geographique))

dep_top <- camb_ann |>
  dplyr::group_by(Zone_geographique) |>
  dplyr::filter(dplyr::n() >= 5) |>
  dplyr::summarise(m = mean(Valeurs), .groups = "drop") |>
  dplyr::slice_max(m, n = 6) |>
  dplyr::pull(Zone_geographique)

camb_ann |>
  dplyr::filter(Zone_geographique %in% dep_top) |>
  ggplot(aes(date, Valeurs, colour = Zone_geographique)) +
  geom_line(linewidth = 0.9) +
  geom_point(size = 1.6) +
  scale_y_continuous(labels = scales::label_number(big.mark = " "), limits = c(0, NA)) +
  scale_colour_brewer(palette = "Dark2", name = NULL) +
  labs(title = "Cambriolages de logement enregistrés par an",
       subtitle = "Les six départements les plus concernés en moyenne",
       x = NULL, y = NULL) +
  theme_barres +
  theme(legend.position = "bottom")

Extraction (séries nationales)

Code
sc |>
  dplyr::filter(Zone_geographique %in% c("France", "France métropolitaine")) |>
  dplyr::transmute(Série = Titre, Zone = Zone_geographique, Périodicité = Periodicite,
                   Période = Unite_temps, Grandeur = Ordonnees, Valeur = Valeurs) |>
  dplyr::arrange(Série, Zone, Période) |>
  reactable::reactable(
    searchable = TRUE, filterable = TRUE, pagination = TRUE,
    defaultPageSize = 20, compact = TRUE, resizable = TRUE, highlight = TRUE, wrap = TRUE,
    defaultColDef = reactable::colDef(
      align = "left", headerStyle = list(fontWeight = "bold"),
      style = list(whiteSpace = "normal", wordBreak = "break-word")),
    columns = list(
      Série       = reactable::colDef(minWidth = 240),
      Zone        = reactable::colDef(minWidth = 110, maxWidth = 170),
      Périodicité = reactable::colDef(minWidth = 85, maxWidth = 110),
      Période     = reactable::colDef(minWidth = 70, maxWidth = 95),
      Grandeur    = reactable::colDef(minWidth = 130, maxWidth = 200),
      Valeur      = reactable::colDef(minWidth = 75, maxWidth = 120, align = "right",
                                      format = reactable::colFormat(separators = TRUE, digits = 2)))
  )

Les séries départementales et régionales (annuelles) restent dans le fichier Parquet source.

Source & données

Code
tibble::tibble(
  ` ` = c("Jeu de données", "Producteur", "Licence", "Fichier", "Mise à jour", "Observations"),
  `  ` = c(
    "Séries chronologiques diffusées par le SSMSI sur [data.gouv.fr](https://www.data.gouv.fr/organizations/ministere-de-linterieur/) (`serieschrono`)",
    "Service statistique ministériel de la sécurité intérieure (SSMSI) — ministère de l'Intérieur",
    "Licence Ouverte / Open Licence 2.0 (Etalab)",
    sprintf("`data/ssmsi/serieschrono-datagouv.parquet` (%s ko)", round(file.size(sc_path) / 1024)),
    format(as.Date(file.mtime(sc_path)), "%d %B %Y"),
    sprintf("%s lignes, %d séries, %d zones, %d–%d", format(nrow(sc), big.mark = " "),
            n_series, n_zones, annee[1], annee[2])
  )
) |>
  gt::gt() |> gt::fmt_markdown(columns = `  `) |>
  gt::tab_options(table.width = "100%", column_labels.hidden = TRUE)
Jeu de données Séries chronologiques diffusées par le SSMSI sur data.gouv.fr (serieschrono)
Producteur Service statistique ministériel de la sécurité intérieure (SSMSI) — ministère de l’Intérieur
Licence Licence Ouverte / Open Licence 2.0 (Etalab)
Fichier data/ssmsi/serieschrono-datagouv.parquet (360 ko)
Mise à jour 06 septembre 2026
Observations 88 361 lignes, 160 séries, 105 zones, 2006–2026