Le découpage de fichiers « à plat »

Le PMSI utilise principalement des fichiers que je qualifie de « à plat ». C’est à dire qu’il s’agit de fichiers textes dont les « observations » (au sens R) sont délimitées par des retours à la ligne mais chaque observation est dans un format fixe normé (dans les fameux fichiers excel de l’ATIH, par exemple format_mco2022.xlsx).

Pour notre exemple du jour nous allons prendre le cas du fichier IUM tout simplement car c’est celui qui contient le moins de colonnes.

Un fichier IUM typique contient 1 ligne par service et ressemble à ca :

2055990780529   01012020   C
110199078052911 01012020005M
350599078052907 01012020   C
120199078052911 01012020   M
325299078052903 01012020   C
225199078052911 01012020   P
205199078052911 01012020   P
1002990780529   01012020   C

Pour un oeil aguerri, on peut retrouver la structure :

110199078052911 01012020005M
1101 = code de l'UM
990780529 = FINESS géographique
11 = code d'autorisation
01012020 = date d'effet de l'autorisation au format préféré de l'ATIH "jjmmaaaa"
5 = Nombre de lits dans l'UM
M = mode d'hospitalisation (M = mixte, P = partielle, C = complète)

La lecture du fichier excel de l’ATIH nous donne les définitions officielles ainsi que les tailles et positions de découpe :

Passons aux choses sérieures

Commençons par charger les libraires incontournables

library(dplyr)
library(magrittr)

et ajoutons-y la librairie readr, qui comme les précédentes fait partie du tidyverse

library(readr)

Readr donne accès à plusieurs fonctions de lecture de fichiers selon des formats variables.

Celle qui nous intéresse est read_fwf qui permet de lire des formats à champs de longueur prédéfinie (Fixed Width Field)

Si on regarde l’aide de la fonction, on trouve :

read_fwf(
  file,
  col_positions,
  col_types = NULL,
  locale = default_locale(),
  na = c("", "NA"),
  comment = "",
  trim_ws = TRUE,
  skip = 0,
  n_max = Inf,
  guess_max = min(n_max, 1000),
  progress = show_progress(),
  skip_empty_rows = TRUE
)

Nous devons donc fournir au minimum, un nom de fichier (file) et un ensemble de positions (col_positions) pour le découpage. Les autres paramètres sont optionnels.

En se basant sur format_mco2022 et le mode d’emploi de read_fwf, on en déduit un contenu possible de col_positions :

fwf_cols(UM = c(1, 4), FINESS = c(5, 13), AUTH = c(14, 16), DDEB = c(17, 24), NLITS = c(25, 27), MODE = c(28,28))

Ce qui nous donne

read_fwf("fich.UM",fwf_cols(UM = c(1, 4), FINESS = c(5, 13), AUTH = c(14, 16), DDEB = c(17, 24), NLITS = c(25, 27), MODE = c(28,28)))

ou pour gagner en lisibilité :

ums <- read_fwf( file          = "fich.UM",
                 col_positions = fwf_cols(UM     = c(1, 4),
                                          FINESS = c(5, 13),
                                          AUTH   = c(14, 16), 
                                          DDEB   = c(17, 24),
                                          NLITS  = c(25, 27),
                                          MODE   = c(28,28)
                                         )
               )

On voit bien apparaître les données de positionnement telles qu’elles sont dans format_mco.

Pas encore tout à fait ça

Si nous lançons la commande ci-dessus, nous nous retrouvons avec un jeu de données

# A tibble: 32 x 6
      UM    FINESS AUTH  DDEB     NLITS MODE 
   <dbl>     <dbl> <chr> <chr>    <chr> <chr>
 1  2055 990780529 NA    01012020 NA    C    
 2  1101 990780529 11    01012020   5   M    
 3  3505 990780529 07    01012020 NA    C    
 4  1201 990780529 11    01012020 NA    M    
 5  3252 990780529 03    01012020 NA    C    
 6  2251 990780529 11    01012020 NA    P    
 7  2051 990780529 11    01012020 NA    P    
 8  1002 990780529 NA    01012020 NA    C    
 9  1001 990780529 11    01012020 NA    M    
10  2451 990780529 11    01012020 NA    P    
# ... with 22 more rows

Un problème se pose cependant, c’est le type de données de chaque colonne. Readr a essayé de deviner ! Il a décidé que UM et FINESS étaient numériques et le reste des chaines de caractères. Pour cela il étudie environ 1000 lignes et cherche le type qui correspond à toutes, le type character étant celui acceptant tout.

NA représente des données non disponibles, dans le cas présent un champ « vide » (ou plutôt composé uniquement d’espaces).

Or cela ne nous va pas. NLITS est obligatoirement numérique, DDEB est une date et n’ayant pas de calculs à faire dessus, il est préférable que UM et FINESS soient des chaines de caractères.

Pour régler le problème de NLITS, on pourrait demander à read_fwf de faire lui même la correction de type en précisant le paramètre col_types . Il suffit d’y indiquer une chaine de caractère détaillant les types de chaque champ ainsi si nous voulons chaine-chaine-chaine-chaine-numérique-chaine, on assigne « cccnc » à col_types (je le fais dans la fenêtre exécution):

> read_fwf( file          = "DEF.UM",
+           col_positions = fwf_cols(UM     = c(1, 4),
+                                    FINESS = c(5, 13),
+                                    AUTH   = c(14, 16), 
+                                    DDEB   = c(17, 24),
+                                    NLITS  = c(25, 27),
+                                    MODE   = c(28,28)
+           ),
+           col_types="ccccnc"
+ )
# A tibble: 32 x 6
   UM    FINESS    AUTH  DDEB     NLITS MODE 
   <chr> <chr>     <chr> <chr>    <dbl> <chr>
 1 2055  990780529 NA    01012020    NA C    
 2 1101  990780529 11    01012020     5 M    
 3 3505  990780529 07    01012020    NA C    
 4 1201  990780529 11    01012020    NA M    
 5 3252  990780529 03    01012020    NA C    
 6 2251  990780529 11    01012020    NA P    
 7 2051  990780529 11    01012020    NA P    
 8 1002  990780529 NA    01012020    NA C    
 9 1001  990780529 11    01012020    NA M    
10 2451  990780529 11    01012020    NA P    
# ... with 22 more rows

Reste le problème de la date qui n’est pas au format standard (R utilise le format aaaa-mm-dd par défaut). Malheureusement, même si il existe la lecture directe au format date grâce au caractère « D » dans le col_types, cela ne se passe pas bien :

Warning: 32 parsing failures.
row  col   expected   actual file
  1 DDEB date like  01012020 'DEF.UM'
  2 DDEB date like  01012020 'DEF.UM'
  3 DDEB date like  01012020 'DEF.UM'
  4 DDEB date like  01012020 'DEF.UM'
  5 DDEB date like  01012020 'DEF.UM'
... .... .......... ........ ........
See problems(...) for more details.

Nous allons donc faire appel à une fonction de dplyr pour corriger ce type : mutate et à la fonction de formatage as.Date.

mutate permet de définir de nouvelles colonnes ou de redéfinir des colonnes existantes à partir de valeurs du jeu de données ou des variables du programme.

as.Date modifie le type de données et dans le cas présent va nous servir à transformer une chaine de caractère en date au format R. Nous savons que R ne sait pas comment lire cette date donc nous allons devoir lui expliquer en précisant le format via le paramètre du même nom

> as.Date("02012020", format =  "%d%m%Y")
[1] "2020-01-02"

C’est bon le format est le bon. Il suffit de reformater l’ensemble de la colonne DDEB grâce à mutate

ums %>% mutate(DDEB = as.Date(DDEB, format = "%d%m%Y")
# A tibble: 32 x 6
   UM    FINESS    AUTH  DDEB       NLITS MODE 
   <chr> <chr>     <chr> <date>     <dbl> <chr>
 1 2055  990780529 NA    2020-01-01    NA C    
 2 1101  990780529 11    2020-01-01     5 M    
 3 3505  990780529 07    2020-01-01    NA C    
 4 1201  990780529 11    2020-01-01    NA M    
 5 3252  990780529 03    2020-01-01    NA C    
 6 2251  990780529 11    2020-01-01    NA P    
 7 2051  990780529 11    2020-01-01    NA P    
 8 1002  990780529 NA    2020-01-01    NA C    
 9 1001  990780529 11    2020-01-01    NA M    
10 2451  990780529 11    2020-01-01    NA P    
# ... with 22 more rows

et il suffit de le réattribuer à lui-même pour que notre table soit correcte. Ni vu ni connu.

ums <- ums %>% mutate(DDEB = as.Date(DDEB, format = "%d%m%Y")

Bien sûr il est possible de réaliser l’ensemble en une seule ligne en rajoutant le %>% mutate(DDEB = as.Date(DDEB, format = "%d%m%Y") juste au bout du read_fwf() :

ums <- read_fwf( file          = "DEF.UM",
                 col_positions = fwf_cols(UM     = c(1, 4),
                                          FINESS = c(5, 13),
                                          AUTH   = c(14, 16), 
                                          DDEB   = c(17, 24),
                                          NLITS  = c(25, 27),
                                          MODE   = c(28,28)
                                         ), 
                 col_types="ccccnc"
 ) %>% mutate( DDEB = as.Date(DDEB, format = "%d%m%Y"))

C’est la puissance du pipe %>% !

En lisant des fichiers ATIH contenant des prix, nous aurons souvent ce genre de modifications à faire car les formats ne représentent pas la virgule mais utilisent plutôt une mantisse fixe (2 ou 3 chiffres « après la virgule »). Il convient alors de « muter » les colonnes en les divisant respectivement par 100 ou 1000.

Conclusion

Nous avons vu aujourd’hui comment lire un fichier type du PMSI. Il suffit de décliner en fonction du format désiré.

Cette solution ne sait pas s’adapter automatiquement à la version de fichier, mais vous savez au moins comment convertir les salmigondis d’un fichier ATIH en un jeu de données correctement structuré. Bravo !

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *