Dépenses de soins de ville en date de soins — séries mensuelles CVS-CJO

Données - Assurance Maladie (Cnam)

Code
source(here::here("_rinit.R"))
invisible(Sys.setlocale("LC_TIME", "fr_FR.UTF-8"))
# cd ~/iCloud/website/data/cnam; /usr/local/bin/R -e 'quarto::quarto_render("depenses_date_soins.qmd")'
# (données : Rscript --vanilla cnam.R)

knitr::opts_chunk$set(echo = TRUE, message = FALSE, warning = FALSE,
                      fig.width = 9, fig.height = 5.5, dpi = 150)
library(dplyr); library(tidyr); library(ggplot2); library(stringr)

path <- here::here("data", "cnam", "depenses_date_soins.parquet")
d <- arrow::read_parquet(path)

mdd <- scales::label_number(scale = 1e-9, suffix = " Md€", accuracy = 0.1, big.mark = " ", decimal.mark = ",")
mde <- scales::label_number(scale = 1e-6, suffix = " M€", accuracy = 1, big.mark = " ", decimal.mark = ",")
pct <- scales::label_percent(accuracy = 0.1, decimal.mark = ",")
nb  <- scales::label_number(big.mark = " ", decimal.mark = ",")

theme_cnam <- theme_minimal(base_size = 12) +
  theme(plot.title = element_text(face = "bold", size = 13), plot.title.position = "plot",
        plot.subtitle = element_text(colour = "grey35"),
        panel.grid.minor = element_blank(), legend.position = "bottom")
col1 <- "#1b7f79"; col2 <- "#c0392b"

# barres horizontales triées
barh <- function(df, lab, val, fmt = mdd, fill = col1, ...) {
  df |>
    mutate(.l = forcats::fct_reorder({{ lab }}, {{ val }})) |>
    ggplot(aes(.l, {{ val }})) +
    geom_col(fill = fill, ...) +
    geom_text(aes(label = fmt({{ val }})), hjust = -0.08, size = 3) +
    coord_flip() +
    scale_y_continuous(labels = fmt, expand = expansion(mult = c(0, 0.2))) +
    labs(x = NULL, y = NULL) + theme_cnam
}
Code
s <- d
mens <- s |> filter(type == "mensuel")
cum  <- s |> filter(type == "cumul")
mois_max <- max(mens$mois); mois_min <- min(mens$mois)
main <- cum |> filter(is.na(poste_parent), poste != "Total") |> pull(poste)

Remboursements de soins de ville en date de soins, régime général + anciens affiliés, corrigés des jours ouvrés et des variations saisonnières (séries CVS-CJO), publiés par la Cnam-DSES avec le label de l’Autorité de la statistique publique. Période : janvier 2026 – mai 2026, 18 grands postes et leurs sous-postes. Les montants sont exprimés en euros (le fichier annonce des « milliers d’euros », mais l’ordre de grandeur — 47,6 Md€ cumulés sur 5 mois — montre qu’il s’agit d’euros). La colonne pcap est l’évolution en « pour la même période de l’année précédente » (PCAP) du cumul.

Dépense totale et cumul

Code
mens |> filter(poste == "Total") |>
  ggplot(aes(mois, montant)) + geom_col(fill = col1, width = 20) +
  geom_text(aes(label = mdd(montant)), vjust = -0.4, size = 3.5) +
  scale_y_continuous(labels = mdd, expand = expansion(mult = c(0, .12))) +
  scale_x_date(date_labels = "%b %Y", breaks = unique(mens$mois)) +
  labs(x = NULL, y = NULL, title = "Remboursements de soins de ville, en date de soins (CVS-CJO)") + theme_cnam

Le montant de janvier est nettement supérieur aux autres mois (10,6 Md€ contre environ 9,3 Md€ ensuite) : le pic vient des généralistes et du poste « Autres » et correspond vraisemblablement à des paiements de forfaits annuels versés en début d’année.

Les grands postes

Code
mens |> filter(poste %in% setdiff(main, "Total"), poste != "Autres") |>
  ggplot(aes(mois, montant)) + geom_line(colour = col1, linewidth = 0.9) + geom_point(colour = col1) +
  facet_wrap(~poste, scales = "free_y", ncol = 3) +
  scale_y_continuous(labels = scales::label_number(scale = 1e-6, suffix = " M€")) +
  scale_x_date(date_labels = "%b", breaks = unique(mens$mois)) +
  labs(x = NULL, y = NULL, title = "Remboursements mensuels par poste (hors « Autres »)") + theme_cnam

Code
cum |> filter(is.na(poste_parent), poste != "Total") |> mutate(l = forcats::fct_reorder(poste, montant)) |>
  ggplot(aes(l, montant, fill = pcap > 0)) + geom_col(show.legend = FALSE) +
  geom_text(aes(label = paste0(mdd(montant), "  (", scales::label_percent(accuracy = 0.1, decimal.mark = ",", style_positive = "plus")(pcap), ")")),
            hjust = -0.05, size = 3) +
  coord_flip() + scale_fill_manual(values = c(`TRUE` = col1, `FALSE` = col2)) +
  scale_y_continuous(expand = expansion(mult = c(0, .35))) +
  labs(x = NULL, y = NULL, title = "Cumul depuis le 1er janvier et évolution par rapport à la même période de l'an dernier",
       subtitle = "entre parenthèses : PCAP") + theme_cnam

Les psychologues affichent la plus forte croissance ; les transports, les cures thermales et les soins à l’étranger reculent.

Ce qui compte dans le total

Code
tot <- cum$montant[cum$poste == "Total"]; tot_pcap <- cum$pcap[cum$poste == "Total"]
cum |> filter(is.na(poste_parent), poste != "Total") |>
  mutate(part = montant / tot, contribution = montant / tot * pcap / (1 + pcap) * (1 + tot_pcap)) |>
  arrange(desc(montant)) |>
  transmute(Poste = poste, `Cumul` = montant, `Part du total` = part, PCAP = pcap) |>
  gt::gt() |> gt::fmt_number(`Cumul`, decimals = 0, sep_mark = " ") |>
  gt::fmt_percent(c(`Part du total`, PCAP), decimals = 1) |> gt::tab_options(table.width = "100%")
Poste Cumul Part du total PCAP
Médicaments 13 068 495 467 27.4% 4.1%
Indemnités journalières (IJ) 7 302 152 551 15.3% 2.2%
Spécialistes 6 461 349 450 13.6% 0.5%
Infirmières 3 809 878 158 8.0% 2.2%
Liste des produits et prestations (LPP) 3 779 823 775 7.9% 3.9%
Généralistes 3 762 400 396 7.9% 11.4%
Transports 2 380 317 970 5.0% −3.2%
Kinésithérapeutes 2 188 210 374 4.6% 6.1%
Dentistes 1 878 980 171 3.9% 1.9%
Laboratoires 1 329 958 183 2.8% 4.0%
Autres 716 508 349 1.5% −18.2%
Orthophonistes 387 809 025 0.8% 2.5%
Sages-femmes 235 576 637 0.5% 3.9%
Orthoptistes 115 141 272 0.2% 5.0%
Cures thermales 97 243 222 0.2% −15.9%
Psychologues 58 920 889 0.1% 47.5%
Pédicures 39 018 396 0.1% 9.3%
Remboursement de soins à l'étranger 21 352 462 0.0% −16.5%

Révisions des séries

Chaque publication mensuelle révise les mois précédents (remboursements tardifs rattachés à la date de soins).

Code
mens |> filter(!is.na(taux_revision), is.na(poste_parent), poste != "Total") |>
  ggplot(aes(mois, poste, fill = taux_revision)) + geom_tile() +
  geom_text(aes(label = sprintf("%.2f", taux_revision * 100)), size = 3) +
  scale_fill_gradient2(low = col2, mid = "white", high = col1, labels = scales::label_percent()) +
  scale_x_date(date_labels = "%b %Y", breaks = unique(mens$mois)) +
  labs(x = NULL, y = NULL, fill = "Révision", title = "Taux de révision par rapport à la publication précédente (en %)") + theme_cnam

Source

Caisse nationale de l’Assurance Maladie (Cnam) — Cnam-DSES — Séries de remboursements de soins de ville en date de soins, séries statistiques labellisées par l’Autorité de la statistique publique. Conversion Excel → Parquet : cnam.R (dossier data/cnam).