R/eacr.R

Defines functions millesimeEacr ecarterCellulesFautivesEacr lireEacr tablesEacr

Documented in lireEacr millesimeEacr tablesEacr

# Lecture de l'EACR embarquee dans le paquet.
#
# L'EACR -- enquete annuelle aupres des caisses de retraite, enrichie du modele
# Ancetre -- est diffusee en open data par la Drees sous la forme de deux
# classeurs Excel de 24 et 16 Mo. Le depot n'embarque pas ces classeurs mais le
# resultat de leur lecture : une table par feuille, au format parquet, sous
# inst/extdata/drees/eacr. C'est le parti pris pour les baremes de l'IPP, pour
# la meme raison -- on versionne ce qu'on relit, pas ce qu'on telecharge.
#
# L'import qui produit ces parquets vit dans tools/importer-eacr.R et se
# relance a la main a chaque millesime : il demande le reseau, et l'EACR ne
# nourrit aucun calcul du modele. Elle ne sert qu'a poser une reference
# observee sur les figures, d'ou une lecture a la volee dans la documentation
# et les rapports, hors du pipeline et hors du cache.

#' Tables de l'EACR embarquées dans le paquet
#'
#' Registre des tables de l'enquête annuelle auprès des caisses de retraite :
#' une ligne par feuille des deux classeurs diffusés par la Drees. Il fait le
#' lien entre le nom de feuille de la source et le fichier que le paquet
#' embarque, et sert donc autant à `lireEacr()` qu'au script d'import.
#'
#' @return une `data.table` d'une ligne par table, de clé `table`
#' @examples
#' # Le registre des tables de l'EACR embarquées dans le paquet
#' tablesEacr()
#' @export
tablesEacr <- function() {
  registre <- data.table::data.table(
    table = c(
      "A-Cadrage",
      "A-Prelevements_sociaux",
      "A-Age_conjoncturel",
      "B-Liquidants",
      "C-Droits_directs",
      "D-Droits_derives",
      "E-Minima",
      "G-Cumul",
      "H-Conditions_liquidation",
      "Coefficients_Agirc-Arrco",
      "I-Invalidite",
      "J-Rentes_AT-MP"
    ),
    partie = c(1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 2L, 2L, 2L),
    feuille = c(
      "A-Cadrage",
      "A-Prelev_sociaux",
      "A-Age_conjoncturel",
      "B-Liquidants",
      "C-Droits_directs",
      "D-Droits_d\u00e9riv\u00e9s",
      "E-Minima",
      "G-Cumul",
      "H-Conditions_liq",
      "Coeff Agirc-Arrco",
      "I-Invalidit\u00e9",
      "J-Rentes AT-MP"
    ),
    fichier = c(
      "a-cadrage.parquet",
      "a-prelevements-sociaux.parquet",
      "a-age-conjoncturel.parquet",
      "b-liquidants.parquet",
      "c-droits-directs.parquet",
      "d-droits-derives.parquet",
      "e-minima.parquet",
      "g-cumul.parquet",
      "h-conditions-liquidation.parquet",
      "coefficients-agirc-arrco.parquet",
      "i-invalidite.parquet",
      "j-rentes-at-mp.parquet"
    ),
    contenu = c(
      paste(
        "effectifs et masses par caisse et sexe, en stock et en flux",
        "(liquidants, primo-liquidants, d\u00e9c\u00e8s), depuis 2004"
      ),
      paste(
        "b\u00e9n\u00e9ficiaires par pr\u00e9l\u00e8vement (CSG, CRDS, Casa,",
        "cotisation maladie) et par taux"
      ),
      "\u00e2ge conjoncturel de d\u00e9part, par r\u00e9gime et sexe, depuis 2004",
      paste(
        "liquidants par \u00e2ge, d\u00e9cote / surcote / taux plein et nombre de",
        "trimestres, depuis 2005"
      ),
      paste(
        "d\u00e9tail de A par \u00e2ge et zone de naissance, avec l'\u00e2ge de",
        "liquidation et les effectifs en \u00e9quivalent carri\u00e8re compl\u00e8te"
      ),
      "m\u00eame d\u00e9tail pour les droits d\u00e9riv\u00e9s",
      paste(
        "b\u00e9n\u00e9ficiaires du mico, du miga et de la pmr, par sexe et",
        "trimestres, depuis 2006"
      ),
      paste(
        "cumul emploi-retraite et retraite progressive, par sexe et \u00e2ge,",
        "depuis 2008"
      ),
      paste(
        "stock de retrait\u00e9s par motif de d\u00e9part et par taux,",
        "de 2010 \u00e0 2024"
      ),
      "retrait\u00e9s concern\u00e9s par les coefficients temporaires, 2019-2023",
      paste(
        "b\u00e9n\u00e9ficiaires de pensions d'invalidit\u00e9 par cat\u00e9gorie,",
        "en stock et en flux, de 2010 \u00e0 2024"
      ),
      paste(
        "rentes d'accident du travail et de maladie professionnelle,",
        "depuis 2020"
      )
    )
  )
  data.table::setkey(registre, table)
  registre[]
}

#' Lecture d'une table de l'EACR
#'
#' Relit une table de l'EACR embarquée dans le paquet, telle que la Drees la
#' diffuse : aucune ligne n'est écartée, aucune valeur n'est recodée. Seuls les
#' noms de colonnes sont normalisés (`Champ_FluxStock` devient
#' `champFluxStock`) et leurs types posés à l'import.
#'
#' @details
#' Quatre traits de la source à connaître avant de superposer une de ses séries
#' à une sortie du modèle :
#'
#' - une même année figure **deux fois** dans `A-Cadrage`, dans l'enquête de
#'   l'année puis actualisée dans celle de l'année suivante ; la colonne
#'   `plusRecent` désigne la plus récente des deux, mais les tables `C` et `D`
#'   ne sont alors plus cohérentes avec `A` ;
#' - les effectifs sont des **stocks au 31 décembre** et les montants ceux de
#'   décembre : le produit effectif × pension × 12 n'est pas la masse versée
#'   dans l'année ;
#' - la variation du stock d'une année sur l'autre **n'égale pas** les nouveaux
#'   retraités moins les décès, dans la plupart des régimes et des années ; la
#'   documentation de la source quantifie ce défaut de bouclage ;
#' - les régimes sont désignés par leur code caisse `cc`, comme dans l'EIC et
#'   l'EIR, mais l'EACR agrège là où l'EIR détaille (`"1000"` pour l'Ircantec,
#'   `"2100"` pour la CNAVPL) et couvre des organismes que le modèle ne connaît
#'   pas (`"0000"` tous régimes confondus, la CNAM, la CNBF, la CSS Mayotte).
#'
#' Les cases de moins de onze personnes sont déjà à valeur manquante dans la
#' source.
#'
#' Toute réutilisation doit porter la mention « Source : Drees, enquête
#' annuelle auprès des caisses de retraite (EACR), enrichie avec le modèle
#' Ancetre ».
#'
#' Chaque table n'est lue qu'une fois par session, et la fonction rend une
#' copie : la modifier par référence n'atteint pas la lecture suivante.
#'
#' Les cellules que la source publie de travers sont écartées à la lecture, et
#' un avertissement les nomme (voir `cellulesFautivesEacr`). `brut = TRUE` rend
#' la table telle que la source la publie, défauts compris.
#'
#' @param table le nom de la table, parmi `tablesEacr()$table`
#' @param chemin le dossier où lire les parquets ; par défaut celui du paquet
#' @param brut faut-il rendre la table sans écarter les cellules fautives
#' @return une `data.table`
#' @examples
#' # Les noms de tables disponibles
#' tablesEacr()$table
#'
#' # Lire une table (recodage minimal, cellules fautives écartées)
#' cadrage <- lireEacr("A-Cadrage")
#' head(cadrage)
#' @export
lireEacr <- function(table, chemin = NULL, brut = FALSE) {
  # validation des entrees
  registre <- tablesEacr()
  checkmate::assert_choice(table, registre$table)
  if (is.null(chemin)) {
    chemin <- system.file("extdata", "drees", "eacr", package = "legiretraite")
  }
  checkmate::assert_string(chemin, min.chars = 1)
  checkmate::assert_directory_exists(chemin, access = "r")
  checkmate::assert_flag(brut)

  # indexation vectorielle et non `registre[list(table), ]` : dans `i`, le nom
  # `table` designerait la colonne du registre et non l'argument
  fichier <- file.path(
    chemin,
    registre[["fichier"]][registre[["table"]] == table]
  )
  checkmate::assert_file_exists(fichier, access = "r")

  # Le rapport lit une meme table quarante fois, et chaque lecture de parquet
  # laisse derriere elle une memoire qu'arrow ne rend pas : 47 lectures de la
  # plus grosse table coutent 600 Mo de RSS, contre 150 Mo pour autant de
  # copies de la table lue une seule fois. Le job de couverture s'y faisait
  # tuer par le noyau. Les parquets sont livres avec le paquet et ne changent
  # pas en cours de session : les garder est sans risque.
  cle <- paste(chemin, table, sep = "|", if (brut) "brut")
  if (!is.null(cacheLectureEacr[[cle]])) {
    return(data.table::copy(cacheLectureEacr[[cle]]))
  }

  logger::log_debug("Lecture de {basename(fichier)}")
  dt <- data.table::setDT(arrow::read_parquet(fichier))
  if (!brut) {
    dt <- ecarterCellulesFautivesEacr(dt, table)
  }

  # validation des sorties
  checkmate::assert_data_table(dt, min.rows = 1)
  cacheLectureEacr[[cle]] <- dt
  data.table::copy(dt)
}

# Les cellules que la source publie de travers, et que la lecture ecarte. Une
# entree par defaut constate, avec la vague qui le porte : la source est
# corrigee d'une vague a l'autre, et le test « la source porte encore les
# defauts recenses » previendra le jour ou l'une de ces entrees sera devenue
# inutile.
#
# EACR 2023 : pour la MSA salaries en 2022, la colonne de montant moyen recopie
# celle des effectifs -- trois lignes, Ensemble, Femmes et Hommes, ou m1 vaut
# exactement l'effectif. C'est la derniere vague a avoir vu 2022, donc celle que
# la lecture retient : sans ce retrait, la figure du minimum contributif porte un
# montant moyen de 13 227 euros et son axe ecrase toutes les autres courbes.
cellulesFautivesEacr <- data.table::data.table(
  table = "E-Minima",
  source = "EACR 2023",
  cc = "0021",
  annee = 2022L,
  type = "mico_trait\u00e9_servi",
  defaut = "le montant moyen recopie l'effectif"
)

# Retire d'une table les cellules recensees comme fautives, et le dit.
#
# `nomTable` et non `table` : le registre a une colonne de ce nom, qui masquerait
# l'argument dans `i`.
ecarterCellulesFautivesEacr <- function(dt, nomTable) {
  fautives <- cellulesFautivesEacr[table == nomTable]
  if (nrow(fautives) == 0L) {
    return(dt)
  }
  cles <- setdiff(names(fautives), c("table", "defaut"))
  aEcarter <- dt[fautives, on = cles, which = TRUE, nomatch = NULL]
  if (length(aEcarter) == 0L) {
    return(dt)
  }
  logger::log_warn(paste(
    "EACR, table {nomTable} : {length(aEcarter)} ligne(s) ecartee(s) a la",
    "lecture, la source les publiant de travers ({fautives$defaut[1]})."
  ))
  dt[!aEcarter]
}

# Les tables deja lues, par chemin et par nom. Vide a chaque demarrage de R :
# rien n'y survit a la session, et rien n'y est ecrit sur disque.
cacheLectureEacr <- new.env(parent = emptyenv())

#' Millésime de l'EACR embarquée
#'
#' Quelle version de la source les parquets du paquet reproduisent, et quand
#' elle a été téléchargée. La mention du millésime accompagne la citation de la
#' source sur les figures.
#'
#' @param chemin le dossier où lire le millésime ; par défaut celui du paquet
#' @return une `data.table` d'une ligne par classeur téléchargé
#' @examples
#' # Millésime (version et date de téléchargement) de l'EACR embarquée
#' millesimeEacr()
#' @export
millesimeEacr <- function(chemin = NULL) {
  if (is.null(chemin)) {
    chemin <- system.file("extdata", "drees", "eacr", package = "legiretraite")
  }
  checkmate::assert_string(chemin, min.chars = 1)
  fichier <- file.path(chemin, "millesime.csv")
  checkmate::assert_file_exists(fichier, access = "r")

  dt <- data.table::fread(file = fichier, colClasses = "character")
  checkmate::assert_names(
    names(dt),
    permutation.of = c("partie", "titre", "url", "dateTelechargement")
  )
  dt[]
}

Try the legiretraite package in your browser

Any scripts or data that you put into this service are public.

legiretraite documentation built on Oct. 7, 2026, 5:09 p.m.