Importer plusieurs feuilles ou fichiers

Satellite
Data Import
Apprenez à importer plusieurs feuilles d’un fichier Excel, ou plusieurs fichiers Excel.

Objectifs

  • Savoir importer des données de plusieurs fichiers différents
  • Savoir importer les données de plusieurs feuilles différentes

Mise en place

Téléchargez et dé-zippez le dossier suivant contenant les données et .Rproj pour ce tutoriel.



Accédez au nouveau dossier et créez un nouveau script nommé import_multiple.R. Ajoutez un en-tête approprié au script et chargez les paquets {rio} et {here}. Créez un objet path_data_raw qui pointe vers le répertoire data/raw du dossier du cours.

Introduction

Vous avez appris à importer plusieurs types de fichiers à l’aide du paquet {rio} lors du cours sur l’import de données. Dans 90% des cas, vous utiliserez ce paquet pour importer des fichiers .xlsx et .csv. Dans ce satellite, nous allons apprendre à importer des données lorsqu’elles sont dispersées entre plusieurs feuilles ou fichiers, ce qui est un problème courant. Par exemple :

  • Vous avez un fichier Excel avec des donnés de linelist, des données d’occupation des lits et des données labo dans différentes feuilles, et vous voudriez tout importer dans R.

  • Vous avez plusieurs fichiers Excel dont vous voudriez importer les données (un fichier linelist, un fichier lab etc.)

  • Vous avec un fichier Excel avec des données dans plusieurs feuilles, mais cette fois toutes les feuilles sont dans le même format, et il vous faut toutes les importer.

  • Vous avez plusieurs fichiers Excel tous formatés de la même manière (une linelist par site par exemple) et vous voudriez importer toutes les données et les rassembler dans un même data frame.

On peut résoudre les deux premiers cas avec des commandes différentes visant les feuilles ou fichiers différents, et stocker les données dans des data frames différents. C’est valable s’il n’y a pas trop de jeux de données différents. En revanche, les deux cas suivants peuvent être automatisés !

Dans ce tutoriel, nous allons voir comment le paquet {rio} règle ces problèmes avec la fonction import_list(), qui peut importer plusieurs éléments d’un coup. Ici, les éléments peuvent être des fichiers ou des feuilles, en fonction de quels arguments on passe à la fonction.

Importer plusieurs feuilles

La fonction import_list() peut importer toutes les feuilles d’un fichier Excel et les stocker dans une liste. Celà évite d’avoir à écrire une commande import() différente par feuille.

# NE PAS EXÉCUTER (PSEUDO-CODE)

# Renvoi une liste de data frames
import_list(here(path_data_raw, "example_linelist.xlsx")) 

Imaginons que vous ayez un fichier labo avec les patients positifs, négatifs et indéterminés sur trois feuilles différentes et vous voulez importer ces données.

Allez dans le sous-dossier data/raw/satellite_multiple/multiple_sheets/. Il y a deux versions des données labo. Ouvrez les deux fichiers et inspectez leur structure. Voyez-vous en quoi les deux fichier diffèrent ?

On va commencer par le premier fichier, qui est un peu plus propre.

Utilisez la fonction import_list() pour importer toutes les feuilles de msf_laboratory_moissala_2023-09-24_first.xlsx d’un coup, et stockez ces données dans un objet lab_data_list.

L’objet lab_data_list est une liste qui contient plusieurs data frames (un data frame par feuille). Il est possible que vous ne soyez pas encore familiarisés avec les listes. Pour le moment nous nous contenterons de jeter un œil à la structure de l’objet avec des fonctions que vous connaissez :

# Structure de la liste :
str(lab_data_list)     
List of 3
 $ inconclusive:'data.frame':   58 obs. of  5 variables:
  ..$ MSF Number ID    : num [1:58] 147 414 455 469 533 ...
  ..$ Laboratory id    : chr [1:58] "BOU-4" "MOI-5" "DAN-12" "DAN-13" ...
  ..$ Date of the test : POSIXct[1:58], format: "2022-10-12 18:40:49" "2022-12-09 05:55:56" ...
  ..$ CT value         : logi [1:58] NA NA NA NA NA NA ...
  ..$ Final Test Result: chr [1:58] "inconclusive" "inconclusive" "inconclusive" "inconclusive" ...
 $ negative    :'data.frame':   101 obs. of  5 variables:
  ..$ MSF Number ID    : num [1:101] 13 397 416 490 767 ...
  ..$ Laboratory id    : chr [1:101] "DAN-1" "DAN-9" "BOU-12" "BEK-12" ...
  ..$ Date of the test : POSIXct[1:101], format: "2022-09-10 21:25:04" "2022-11-21 20:02:44" ...
  ..$ CT value         : logi [1:101] NA NA NA NA NA NA ...
  ..$ Final Test Result: chr [1:101] "negative" "negative" "negative" "negative" ...
 $ positive    :'data.frame':   888 obs. of  5 variables:
  ..$ MSF Number ID    : num [1:888] 1 10 21 39 62 71 73 75 105 107 ...
  ..$ Laboratory id    : chr [1:888] "BED-1" "KOU-1" "DAN-2" "BEK-1" ...
  ..$ Date of the test : POSIXct[1:888], format: "2022-08-20 00:00:00" "2022-09-04 15:21:36" ...
  ..$ CT value         : num [1:888] 27 26.9 28.1 27.2 26.3 26 27.8 28.9 26.8 25.9 ...
  ..$ Final Test Result: chr [1:888] "positive" "positive" "positive" "positive" ...
# Nb d'éléments de la liste :
length(lab_data_list)  
[1] 3
# Nom des éléments de la liste :
names(lab_data_list)   
[1] "inconclusive" "negative"     "positive"    

Vous pouvez aussi cliquer sur le nom de la liste dans le panneau Environment pour inspecter sa structure et l’ouvrir dans le Viewer, comme on l’a fait pour les data frames.

Vous noterez que la View est un peu différente. Cliquez sur les boutons que vous voyez pour voir ce qui se passe.

Indice : Il y a des boutons à la gauche et à la droite du panneau Viewer, mais ceux de droite n’apparaissent que quand on passe la souris sur la ligne.

Vous pouvez accéder aux éléments d’une liste avec ces commandes

# Accéder à l'élément par son nom :
head(lab_data_list$positive)  
  MSF Number ID Laboratory id    Date of the test CT value Final Test Result
1             1         BED-1 2022-08-20 00:00:00     27.0          positive
2            10         KOU-1 2022-09-04 15:21:36     26.9          positive
3            21         DAN-2 2022-09-12 00:54:38     28.1          positive
4            39         BEK-1 2022-09-15 02:31:22     27.2          positive
5            62         BOU-1 2022-09-30 17:51:01     26.3          positive
6            71         BEK-2 2022-10-02 15:03:51     26.0          positive
# Accéder au contenu du premier élément :
head(lab_data_list[[1]])  
  MSF Number ID Laboratory id    Date of the test CT value Final Test Result
1           147         BOU-4 2022-10-12 18:40:49       NA      inconclusive
2           414         MOI-5 2022-12-09 05:55:56       NA      inconclusive
3           455        DAN-12 2022-11-22 17:09:53       NA      inconclusive
4           469        DAN-13 2022-11-18 07:03:57       NA      inconclusive
5           533         MOI-9 2023-01-05 03:38:26       NA      inconclusive
6           882        DAN-21 2023-02-05 21:57:09       NA      inconclusive
# Accéder au premier élément de la liste (par position) :
lab_data_list[1]  
$inconclusive
   MSF Number ID  Laboratory id    Date of the test CT value Final Test Result
1            147          BOU-4 2022-10-12 18:40:49       NA      inconclusive
2            414          MOI-5 2022-12-09 05:55:56       NA      inconclusive
3            455         DAN-12 2022-11-22 17:09:53       NA      inconclusive
4            469         DAN-13 2022-11-18 07:03:57       NA      inconclusive
5            533          MOI-9 2023-01-05 03:38:26       NA      inconclusive
6            882         DAN-21 2023-02-05 21:57:09       NA      inconclusive
7           1070         BEK-20 2023-01-29 19:46:36       NA      inconclusive
8           1163         BEK-25 2023-02-08 01:12:21       NA      inconclusive
9           1326         BED-11 2023-02-23 22:48:47       NA      inconclusive
10          1600         DAN-31 2023-03-03 12:47:31       NA      inconclusive
11          1869         MOI-50 2023-03-18 00:41:43       NA      inconclusive
12          1948         MOI-56 2023-04-01 08:46:37       NA      inconclusive
13          1987         BOU-42 2023-03-16 15:04:54       NA      inconclusive
14          2242         MOI-73 2023-03-27 10:37:40       NA      inconclusive
15          2268         MOI-74 2023-04-07 13:24:03       NA      inconclusive
16          2449         BOU-48 2023-04-29 23:22:57       NA      inconclusive
17          2536         MOI-91 2023-04-07 11:09:42       NA      inconclusive
18          2928        MOI-124 2023-04-21 09:21:41       NA      inconclusive
19          3068        MOI-136 2023-04-25 03:29:41       NA      inconclusive
20          3580        MOI-166 2023-05-07 09:01:07       NA      inconclusive
21          3759        MOI-175 2023-06-02 18:53:39       NA      inconclusive
22          3820        MOI-177 2023-05-12 06:22:39       NA      inconclusive
23          3928         BED-61 2023-06-09 07:45:48       NA      inconclusive
24          4050         BOU-74 2023-06-18 12:26:59       NA      inconclusive
25          4316         BED-70 2023-06-02 23:11:48       NA      inconclusive
26          4331         BOU-80 2023-06-06 15:23:00       NA      inconclusive
27          4609        MOI-216 2023-07-14 06:03:25       NA      inconclusive
28          4680        MOI-225 2023-07-19 17:24:27       NA      inconclusive
29          4721        MOI-229 2023-07-22 12:43:41       NA      inconclusive
30          5025         BOU-86 2023-06-05 04:24:20       NA      inconclusive
31          5111        MOI-249 2023-08-04 17:51:58       NA      inconclusive
32          5283        MOI-261 2023-08-12 09:09:33       NA      inconclusive
33          5337         BED-91 2023-06-28 16:46:56       NA      inconclusive
34          5634        MOI-281 2023-08-27 08:31:02       NA      inconclusive
35          5844        MOI-297 2023-06-23 13:13:51       NA      inconclusive
36          6142        BOU-105 2023-09-06 17:26:24       NA      inconclusive
37          6591        MOI-346 2023-09-26 10:45:51       NA      inconclusive
38          6675        MOI-353 2023-08-02 07:21:04       NA      inconclusive
39          6788        BOU-113 2023-08-31 16:29:50       NA      inconclusive
40          6945        MOI-369 2023-07-13 08:48:11       NA      inconclusive
41          7236        BED-123 2023-07-31 09:45:47       NA      inconclusive
42          7314        MOI-395 2023-08-12 11:32:27       NA      inconclusive
43          7594        MOI-417 2023-08-15 09:00:03       NA      inconclusive
44          7743        MOI-432 2023-09-22 11:59:36       NA      inconclusive
45          7776        MOI-437 2023-10-21 17:40:38       NA      inconclusive
46          7876        MOI-442 2023-09-18 00:03:53       NA      inconclusive
47          7900        MOI-445 2023-08-06 18:07:45       NA      inconclusive
48          8076        MOI-454 2023-10-01 19:23:50       NA      inconclusive
49          8317        MOI-471 2023-08-09 17:52:31       NA      inconclusive
50          8332        MOI-473 2023-08-21 03:51:30       NA      inconclusive
51          8772        MOI-496 2023-09-25 00:47:21       NA      inconclusive
52          8834        MOI-502 2023-10-20 12:12:01       NA      inconclusive
53          9089        BED-156 2023-10-22 04:24:13       NA      inconclusive
54          9090        MOI-519 2023-10-15 19:57:32       NA      inconclusive
55          9564        MOI-550 2023-10-15 12:26:19       NA      inconclusive
56          9859        BED-166 2023-11-06 09:27:17       NA      inconclusive
57          9917        MOI-585 2023-11-12 22:42:52       NA      inconclusive
58          9917 MOI-585_retest 2023-11-14 22:42:52       NA      inconclusive

Concaténer un un data frame

Si vous êtes sûrs que les données dans les différentes feuilles ont toutes la même structure, vous pouvez demander à R de concaténer les différents data frames en un seul à l’aide de l’argument rbind = TRUE [bind veut dire lier en anglais, et le “r” est un raccourcis pour row, la ligne; cette commande concatène les jeux de données par les lignes] :

# NE PAS EXÉCUTER (PSEUDO-CODE)

# Renvoyer un data frame concaténé :
import_list(
  here(path_data_raw, "example_linelist.xlsx"), 
  rbind = TRUE,  # Indique à R de rassembler les données en un seul df
  rbind_label = "Sheet_position"  # Optionnel : garde le nom de la feuille d'origine
)

Utilisez la fonction import_list() pour importer toutes les feuilles de msf_laboratory_moissala_2023-09-24_first.xlsx d’un seul coup, et stockez les données dans un data frame unique lab_data.

Vous pouvez utiliser l’argument rbind_label = et lui fournir une chaîne de caractères pour le nom de la colonne et voir ce qui se passe.

Feuilles un peu différentes

Maintenant nous allons travailler avec le second jeu de données dans le dossier, msf_laboratory_moissala_2023-09-24_second.xlsx. Vous devriez avoir réalisé lors de l’exploration que ces données sont un peu moins propres :

  • la colonne CT value n’existe que sur la troisième feuille
  • la colonne Laboratory id n’est pas au même endroit sur les trois feuilles
  • la date des tests n’a pas le même nom sur les trois feuilles

Que va faire la fonction import_list() dans ces conditions ?

lab_data_messy <- import_list(
  here(path_data_raw, "multiple_sheets",
       "msf_laboratory_moissala_2023-09-24_second.xlsx"),
  rbind = TRUE,
  rbind_label = "Sheet_position")

Exécutez le code de l’encart précédent et utilisez les outils à votre disposition pour inspecter la structure et le contenu de l’objet lab_data_messy :

  • Qu’est devenue la colonne CT value ? A-t-elle été importée ? Si oui, comment est-elle remplie ?
  • Et la colonne Laboratory id ?
  • Que sont devenues les colonnes de dates ? Ont-elles été ignorées ?

Comme vous pouvez le voir, la fonction se comporte assez raisonnablement :

  • La correspondance entre colonnes des différentes feuilles se fait sur leur nom : les variables qui ont le même nom dans les différentes feuilles vont être concaténées quelque soit leurs positions.

  • Le corollaire est que si le nom est un peu différent les colonnes seront traitées comme des variables différentes ! Vérifiez vos noms de variables !

  • Si une colonne n’existe que dans une feuille, elle existera dans le data frame final mais contiendra des valeurs NA pour les individus venant des feuilles ou la variable n’existe pas (rappel sur les valeurs NA).

Maintenant que vous savez comment importer des données de plusieurs feuilles, on va voir comment importer les données de plusieurs fichiers.

Importer plusieurs fichiers

Le paquet {rio} étant bien fait, la fonction import_list() peut aussi recevoir un vecteur contenant des noms de fichiers à importer. Elle importe alors tous les fichier et les stocke sans une liste (sauf si vous utilisez rbind = TRUE ici aussi).

# NE PAS EXÉCUTER (PSEUDO-CODE)

# Créer un vecteur de fichiers (fictifs)
vector_paths <- c(
  here(path_data_raw, "example_linelist_A.xlsx"),
  here(path_data_raw, "example_linelist_B.xlsx"),
  here(path_data_raw, "example_linelist_C.xlsx"),
  here(path_data_raw, "example_linelist_D.xlsx"),
  here(path_data_raw, "example_linelist_E.xlsx"),
  here(path_data_raw, "example_linelist_F.xlsx"),
  here(path_data_raw, "example_linelist_G.xlsx"),
  here(path_data_raw, "example_linelist_H.xlsx"),
  here(path_data_raw, "example_linelist_I.xlsx"),
  here(path_data_raw, "example_linelist_J.xlsx"),
  here(path_data_raw, "example_linelist_K.xlsx"),
  here(path_data_raw, "example_linelist_L.xlsx"),
  here(path_data_raw, "example_linelist_M.xlsx")
)

import_list(vector_paths) # renvoi une liste

Allez dans le sous-dossier data/raw/multiple_files. Ouvrez les fichiers Excel pour inspecter leur contenu. Est-ce que ça aurait du sens de les concaténer en un seul data frame ?

Importez tous les fichiers Excel d’un coup et stockez-les dans un data frame linelist_data.

Inspectez le data frame créé : combien y-a-t’il de lignes ? De colonnes ? Est ce que l’import s’est passé comme vous le souhaitiez ?

Automatiser les chemins d’accès avec file.path()

Lister tous les fichiers

Lister manuellement tous les fichiers que vous vouliez importer, c’était instructif pour comprendre ce que la fonction import_list() attend comme argument, mais dans la vraie vie, c’est fastidieux et il y a un vrai risque de faire des typos et des erreurs.

Nous allons voir comment utiliser la fonction list.files() pour automatiquement générer les chemins d’accès. Son argument principal, path, accepte un chemin de dossier, et renvoi un vecteur contenant les noms des fichiers.

path_mult <- here::here(path_data_raw, "multiple_files") # optionel, pour raccourcir les instructions suivantes

list.files(path = path_mult) # Renvoi un vecteur de noms de fichier
 [1] "moissala_linelist_FR_BEDAYA.xlsx"   "moissala_linelist_FR_BEDJONDO.xlsx"
 [3] "moissala_linelist_FR_BEKOUROU.xlsx" "moissala_linelist_FR_BOUNA.xlsx"   
 [5] "moissala_linelist_FR_GOUNDI.xlsx"   "moissala_linelist_FR_KOUMRA.xlsx"  
 [7] "moissala_linelist_FR_MOISSALA.xlsx" "pad_des_donnes.doc"                
 [9] "pas_des_donnes_non_plus.txt"        "un_document.txt"                   

On peut demander à la fonction de renvoyer les chemins absolus plutôt que les noms de fichier avec l’argument full.names = TRUE :

list.files(path = path_mult,
           full.names = TRUE)
 [1] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_BEDAYA.xlsx"  
 [2] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_BEDJONDO.xlsx"
 [3] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_BEKOUROU.xlsx"
 [4] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_BOUNA.xlsx"   
 [5] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_GOUNDI.xlsx"  
 [6] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_KOUMRA.xlsx"  
 [7] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/moissala_linelist_FR_MOISSALA.xlsx"
 [8] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/pad_des_donnes.doc"                
 [9] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/pas_des_donnes_non_plus.txt"       
[10] "/tmp/RtmpRJBDYU/file780457cda138/data/session-specific/import-multiple/satellite_multiple_FR/data/raw/multiple_files/un_document.txt"                   
Note

Rappelez-vous que grâce à {here} et aux chemins relatifs identiques dans le dossier de cours, ces chemins absolus s’adapteront et fonctionneront également sur votre ordinateur.

Garder seulement les fichiers Excel

Ça commence à être pas mal, mais on a aussi listé d’autres fichiers qui traînaient dans le dossier et qu’on ne veut pas importer. Pour régler ça on va utiliser l’argument pattern de la fonction list.files() [pattern est un motif en anglais]. Ce dernier accepte une chaîne de caractères à rechercher dans les noms des fichiers, pour ne garder que certains d’entre eux. Il accepte des expressions régulières, dites regex, qui vont au delà du contenu de ce tutoriel. Ici nous vous guidons pour créer un motif adapté.

La fonction ne va renvoyer que les noms de fichier qui contiennent le motif donné :

# Tous les fichiers contenant "bedaya" :
list.files(path = path_mult, pattern = "BEDAYA") 
[1] "moissala_linelist_FR_BEDAYA.xlsx"
# Tous les fichiers contenant "msf_linelist" :
list.files(path = path_mult, pattern = "moissala_linelist") 
[1] "moissala_linelist_FR_BEDAYA.xlsx"   "moissala_linelist_FR_BEDJONDO.xlsx"
[3] "moissala_linelist_FR_BEKOUROU.xlsx" "moissala_linelist_FR_BOUNA.xlsx"   
[5] "moissala_linelist_FR_GOUNDI.xlsx"   "moissala_linelist_FR_KOUMRA.xlsx"  
[7] "moissala_linelist_FR_MOISSALA.xlsx"
# Tous les fichiers contenant "msf_ll" :
list.files(path = path_mult, pattern = "msf_ll")  # Rien ne correspond
character(0)
# Tous les fichiers contenant "doc" :
list.files(path = path_mult, pattern = "doc")
[1] "pad_des_donnes.doc" "un_document.txt"   

Vous avez remarqué que dans le dernier cas deux fichiers contiennent “doc” dans leur chemin, l’un dans le nom lui même, l’autre dans l’extension ? Si on veut ne garder que le document Word il faut écrire un motif plus précis.

# Renvoyer le document word
list.files(path = path_mult, pattern = "\\.doc$") 
[1] "pad_des_donnes.doc"

La syntaxe du motif est plus restrictive :

  • Le \\. au début est la manière des regex de dire qu’il devrait y avoir un “.” avant “doc”.

  • Le $ à la fin du motif indique qu’il devrait être trouvé à la fin du nom de fichier.

Ce motif permet donc de chercher les fichiers qui contiennent “.doc” comme extension.

Ecrivez une commande qui utilise list.files() et du regex pour lister tous les fichiers qui finissent en “.xlsx”.

Ensuite, mettez à jour votre code précédent pour importer tous ces fichiers dans R et les concaténer en un seul data frame, sans les répétitions.

Astuce

L’argument recursive = TRUE permet de faire la liste de tous les fichiers présents y compris dans les sous-dossiers de votre chemin ciblé, ce qui peut être pratique si vos données sont rangées dans des sous dossiers.

C’est fini !

Bravo, vous savez maintenant importer plusieurs fichiers ou feuilles d’un seul coup. Ce sera très utile pour importer les données agrégées par semaine ou mois qui doivent vous être familières si vous travaillez avec des données de surveillance.

Peut-être était-ce votre premier contact avec les expressions régulières, qui sont un sujet à part entière, donc ne vous inquiétez pas si cette partie vous semble un peu obscure, c’est quelque chose que vous pourrez apprendre plus tard si vous en avez besoin.

Resources

Une introduction aux expressions régulières