Open Medic 2025 — médicaments remboursés en pharmacie de ville

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("open_medic_2025.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", "open_medic_2025.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
om <- arrow::open_dataset(path)

lab_reg <- tibble::tribble(~BEN_REG, ~region,
  5, "DROM", 11, "Île-de-France", 24, "Centre-Val de Loire", 27, "Bourgogne-Franche-Comté",
  28, "Normandie", 32, "Hauts-de-France", 44, "Grand Est", 52, "Pays de la Loire", 53, "Bretagne",
  75, "Nouvelle-Aquitaine", 76, "Occitanie", 84, "Auvergne-Rhône-Alpes",
  93, "Provence-Alpes-Côte d'Azur et Corse", 99, "Inconnue")
lab_age  <- tibble::tribble(~age, ~tranche, 0, "0-19 ans", 20, "20-59 ans", 60, "60 ans et +", 99, "Inconnu")
lab_sexe <- tibble::tribble(~sexe, ~sexe_lib, 1, "Hommes", 2, "Femmes", 9, "Inconnu")

tot <- om |> summarise(lignes = n(), boites = sum(BOITES), rem = sum(REM), bse = sum(BSE)) |> collect()

Open Medic recense les médicaments délivrés en pharmacie de ville et remboursés par l’Assurance maladie (tous régimes, France entière) en 2025, croisés par classe ATC, présentation (code CIP13), âge (3 tranches), sexe, région de résidence du bénéficiaire et spécialité du prescripteur. Le fichier compte 1 873 062 lignes, pour 27,4 Md€ remboursés (base de remboursement : 31,4 Md€, soit un taux moyen de 87,3%) et 2 327 millions de boîtes.

Note

Les accents des libellés (l_ATC*, l_cip13) sont perdus dans le CSV d’origine (remplacés par ?). Les montants sont en euros ; le champ exclut la rétrocession hospitalière et les médicaments délivrés à l’hôpital.

Dépense par grande classe thérapeutique (ATC1 / ATC2)

Code
om |> group_by(ATC1, l_ATC1) |> summarise(rem = sum(REM), bse = sum(BSE)) |> collect() |> ungroup() |>
  mutate(l = paste0(ATC1, " — ", str_to_sentence(l_ATC1))) |>
  barh(l, rem) +
  labs(title = "Montant remboursé par groupe anatomique principal (ATC1), 2025")

Code
om |> group_by(ATC2, l_ATC2) |> summarise(rem = sum(REM)) |> collect() |> ungroup() |>
  slice_max(rem, n = 20) |>
  mutate(l = paste0(ATC2, " — ", str_to_sentence(str_trunc(l_ATC2, 55)))) |>
  barh(l, rem) +
  labs(title = "Les 20 sous-groupes thérapeutiques (ATC2) les plus remboursés")

Substances et produits les plus remboursés

Code
top_sub <- om |> group_by(ATC5, l_ATC5) |>
  summarise(rem = sum(REM), boites = sum(BOITES), bse = sum(BSE)) |> collect() |> ungroup() |>
  arrange(desc(rem)) |> slice_head(n = 25)

top_sub |>
  transmute(ATC5, Substance = str_to_sentence(l_ATC5), `Remboursé` = rem, `Boîtes` = boites,
            `Remboursé / boîte` = rem / boites, `Taux de remb.` = rem / bse) |>
  gt::gt() |>
  gt::fmt_number(c(`Remboursé`, `Boîtes`), decimals = 0, sep_mark = " ") |>
  gt::fmt_number(`Remboursé / boîte`, decimals = 2) |>
  gt::fmt_percent(`Taux de remb.`, decimals = 1) |>
  gt::tab_options(table.width = "100%")
ATC5 Substance Remboursé Boîtes Remboursé / boîte Taux de remb.
N07XX08 Tafamidis 1 071 300 241 148 965 7,191.62 98.5%
B01AF02 Apixaban 885 426 945 16 032 627 55.23 93.4%
S01LA05 Aflibercept 604 904 031 994 163 608.46 99.8%
L04AB04 Adalimumab 482 161 188 1 139 885 422.99 96.6%
R07AX32 Ivacaftor + tezacaftor + elexacaftor 473 329 536 51 878 9,123.90 99.5%
D11AH05 Dupilumab 434 809 203 454 405 956.88 81.2%
L02BB04 Enzalutamide 403 071 117 140 369 2,871.51 99.8%
A10BK01 Dapagliflozine 388 476 382 10 696 814 36.32 97.1%
N02BE01 Paracetamol 362 981 561 420 269 526 0.86 73.4%
A10BJ06 Semaglutide 348 097 704 4 677 641 74.42 95.7%
C09DX04 Valsartan + sacubitril 346 711 738 3 522 874 98.42 97.5%
L01EB04 Osimertinib 334 255 776 68 167 4,903.48 99.5%
L02BB05 Apalutamide 322 791 490 127 194 2,537.79 99.8%
J07BK03 Antigene purifie de virus du zona 319 313 987 2 324 044 137.40 72.9%
A10BJ05 Dulaglutide 300 251 141 3 758 813 79.88 98.4%
B02BX06 Emicizumab 287 406 633 55 042 5,221.59 99.2%
L01EL01 Ibrutinib 276 349 837 72 180 3,828.62 99.8%
L04AC05 Ustekinumab 264 782 532 190 577 1,389.37 96.5%
R07AX02 Ivacaftor 258 197 112 49 142 5,254.10 99.5%
J05AR20 Emtricitabine + tenofovir alafenamide + bictegravir 242 351 895 288 424 840.26 96.5%
L01EF03 Abemaciclib 223 888 547 132 121 1,694.57 99.2%
L01EJ01 Ruxolitinib 218 974 888 81 931 2,672.67 99.7%
L01EF01 Palbociclib 212 195 432 129 649 1,636.69 99.6%
C10AX13 Evolocumab 190 581 482 901 397 211.43 97.5%
A10AE04 Insuline glargine 181 504 308 4 875 807 37.23 97.9%
Code
om |> group_by(CIP13, l_cip13) |> summarise(rem = sum(REM)) |> collect() |> ungroup() |>
  slice_max(rem, n = 20) |>
  mutate(l = str_to_sentence(str_trunc(l_cip13, 50))) |>
  barh(l, rem, fill = col2) +
  labs(title = "Les 20 présentations (CIP13) les plus remboursées")

Le taux de remboursement moyen (REM / BSE) varie fortement d’une classe à l’autre : il reflète les taux de prise en charge (100 %, 65 %, 30 %, 15 %) des vignettes de médicaments.

Code
om |> group_by(ATC2, l_ATC2) |> summarise(rem = sum(REM), bse = sum(BSE)) |> collect() |> ungroup() |>
  filter(rem > 1e8) |>
  mutate(taux = rem / bse, l = paste0(ATC2, " — ", str_to_sentence(str_trunc(l_ATC2, 50)))) |>
  barh(l, taux, fmt = pct, fill = "#8e6cb8") +
  labs(title = "Taux de remboursement moyen, sous-groupes > 100 M€ remboursés")

Qui consomme ? Âge, sexe, région

Code
om |> group_by(age, sexe) |> summarise(rem = sum(REM)) |> collect() |> ungroup() |>
  left_join(lab_age, by = "age") |> left_join(lab_sexe, by = "sexe") |>
  filter(sexe %in% 1:2) |>
  ggplot(aes(tranche, rem, fill = sexe_lib)) +
  geom_col(position = "dodge") +
  scale_y_continuous(labels = mdd) +
  scale_fill_manual(values = c(Hommes = col1, Femmes = col2)) +
  labs(x = NULL, y = NULL, fill = NULL, title = "Montant remboursé par tranche d'âge et sexe") + theme_cnam

Les 60 ans et plus concentrent près de 60 % du remboursé (les 20-59 ans un tiers, les moins de 20 ans 7 %) : le fichier ne donne pas de populations, on ne calcule donc pas de dépense par habitant (voir la base de population INSEE).

Code
om |> group_by(BEN_REG) |> summarise(rem = sum(REM)) |> collect() |> ungroup() |>
  left_join(lab_reg, by = "BEN_REG") |>
  mutate(region = coalesce(region, "Inconnue")) |>
  barh(region, rem) + labs(title = "Montant remboursé par région de résidence du bénéficiaire")

Prescripteurs

Code
spe <- tibble::tribble(~PSP_SPE, ~prescripteur,
  1, "Médecine générale libérale", 2, "Anesthésie-réanimation", 3, "Cardio-vasculaire", 4, "Chirurgie libérale",
  5, "Dermatologie", 6, "Radiologie", 7, "Gynécologie-obstétrique", 8, "Gastro-entérologie", 9, "Médecine interne",
  11, "ORL", 12, "Pédiatrie", 13, "Pneumologie", 14, "Rhumatologie", 15, "Ophtalmologie", 17, "Psychiatrie",
  18, "Stomatologie", 31, "Médecine physique et réadaptation", 32, "Neurologie", 35, "Néphrologie",
  37, "Anatomo-cytopathologie", 38, "Directeur de laboratoire médecin", 42, "Endocrinologie",
  90, "Prescripteurs salariés (surtout hôpitaux publics)", 98, "Ville hors médecins (dentistes, auxiliaires…)",
  99, "Inconnu")
om |> group_by(PSP_SPE) |> summarise(rem = sum(REM)) |> collect() |> ungroup() |>
  left_join(spe, by = "PSP_SPE") |> mutate(prescripteur = coalesce(prescripteur, as.character(PSP_SPE))) |>
  barh(prescripteur, rem) + labs(title = "Montant remboursé selon la spécialité du prescripteur")

Les prescripteurs salariés (essentiellement hospitaliers : traitements de ville prescrits à l’hôpital, dont les médicaments innovants très coûteux — tafamidis, apixaban, aflibercept… en tête des substances) pèsent près de la moitié du remboursé (49 %), devant les généralistes libéraux (31 %).

Codes « générique »

Code
om |> group_by(TOP_GEN) |> summarise(rem = sum(REM), boites = sum(BOITES)) |> collect() |> ungroup() |>
  mutate(part_rem = rem / sum(rem), part_boites = boites / sum(boites)) |>
  gt::gt() |>
  gt::fmt_number(c(rem, boites), decimals = 0, sep_mark = " ") |>
  gt::fmt_percent(c(part_rem, part_boites), decimals = 1)
TOP_GEN rem boites part_rem part_boites
0 19 647 605 651 1 068 921 549 71.6% 45.9%
1 4 446 797 649 1 073 361 159 16.2% 46.1%
4 3 348 033 842 184 520 569 12.2% 7.9%
9 5 057 1 748 0.0% 0.0%

Dans le fichier 2025 TOP_GEN est numérique (0 = sans objet, 9 = inconnu). Le code 1 pèse 46 % des boîtes mais seulement 16 % du remboursé (médicaments peu chers, profil typique des génériques) ; le code 4 8 % des boîtes et 12 % du remboursé. La correspondance exacte des codes est à confirmer avec le lexique Open Medic.

Source

Caisse nationale de l’Assurance Maladie (Cnam) — Open Medic (data.ameli.fr), base complète 2025. Conversion Excel → Parquet : cnam.R (dossier data/cnam).