4  Importer des données d’enquête

ImportantQuestion de départ

Lorsque le même fichier d’enquête existe en CSV, Excel et Stata, obtient-on réellement les mêmes informations après importation dans R ?

4.1 Objectifs d’apprentissage

À la fin de ce chapitre, vous saurez :

  • choisir une fonction d’importation adaptée aux formats CSV/TXT, Excel, SPSS, Stata et SAS ;
  • utiliser des chemins relatifs depuis le projet ;
  • comprendre la différence entre valeurs, types de colonnes et métadonnées importées ;
  • détecter les problèmes courants de séparateur, encodage et type de colonne ;
  • conserver les labels présents dans les formats statistiques avec {haven} ;
  • réaliser un contrôle systématique juste après l’importation ;
  • comparer deux imports sans supposer qu’ils sont équivalents ;
  • conserver une copie brute et créer des objets de travail distincts.

4.2 Prérequis

Il faut connaître la logique d’un projet, les chemins relatifs, les objets R et les fonctions dim(), names(), class() et glimpse().

4.3 Importer n’est pas une formalité

L’importation semble parfois se résumer à une ligne :

enquete <- readr::read_csv("data/raw/enquete.csv")

Mais cette ligne contient déjà plusieurs décisions : quel séparateur est utilisé ? quelle est la première ligne ? comment les nombres sont-ils écrits ? comment les valeurs vides sont-elles interprétées ? quelles colonnes R déduit-il comme numériques ou textuelles ? les labels d’un fichier statistique ont-ils été conservés ?

Les formats d’enquête ne transportent pas tous la même quantité d’information. Un CSV conserve essentiellement un tableau de valeurs ; un fichier Stata, SPSS ou SAS peut en plus contenir des labels de variables, des labels de valeurs et des informations sur des valeurs manquantes définies par l’utilisateur (Wickham, Miller, et al. 2026).

NotePrincipe directeur

Le but de l’importation n’est pas d’obtenir un objet R « sans erreur ». Le but est d’obtenir un objet dont on sait ce qui a été lu, comment cela a été interprété et quelles informations ont été perdues ou conservées.

4.4 Le kit de données du livre

Le dossier data/raw/ contient plusieurs versions du jeu pédagogique :

data/raw/
├── enqueter_brut.csv
├── enqueter_brut.xlsx
├── enqueter_brut.dta
└── enqueter_vague2.csv

Le fichier principal contient 3 600 lignes et 36 colonnes avant traitement. Les anomalies qu’il contient sont volontaires : il ne faut donc pas « corriger » les données pendant l’importation.

Une version SPSS peut également être générée localement à partir du fichier Stata avec le script R/00-creer-fichiers-riches.R. Cette étape est séparée pour que le dépôt reste utilisable même si tous les formats ne sont pas nécessaires.

4.5 CSV et fichiers texte avec {readr}

{readr} fournit plusieurs fonctions pour lire des données rectangulaires textuelles (Wickham, Hester, et al. 2026) :

  • read_csv() pour les fichiers séparés par des virgules ;
  • read_csv2() pour le format courant en Europe avec ; comme séparateur et , comme marque décimale ;
  • read_tsv() pour les tabulations ;
  • read_delim() lorsque le séparateur doit être indiqué explicitement.

4.5.1 Importer notre CSV

Afficher le code R
library(readr)

enquete_csv <- read_csv(
  "data/raw/enqueter_brut.csv",
  show_col_types = FALSE
)

Juste après l’import :

Afficher le code R
dim(enquete_csv)
[1] 3600   36
Afficher le code R
head(names(enquete_csv), 12)
 [1] "id"                  "strate"              "psu"                
 [4] "poids_base"          "poids_final"         "territoire"         
 [7] "mode_collecte"       "sexe"                "age"                
[10] "diplome"             "statut_emploi"       "situation_familiale"
Afficher le code R
enquete_csv |>
  dplyr::select(id, age, sexe, diplome, revenu_mensuel, poids_final) |>
  dplyr::glimpse()
Rows: 3,600
Columns: 6
$ id             <dbl> 100001, 100002, 100003, 100004, 100005, 100006, 100007,…
$ age            <dbl> 70, 64, 30, 41, 48, 63, 51, 27, 35, 34, 31, 28, 33, 54,…
$ sexe           <dbl> 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, 2, 2…
$ diplome        <dbl> 2, 2, 3, 7, 1, 2, 7, 7, 5, 3, 1, 2, 5, 3, 2, 4, 1, 6, 4…
$ revenu_mensuel <dbl> 1750, 99997, 2600, 3400, 2200, 2350, 99998, 2500, 2350,…
$ poids_final    <dbl> 1.2692, 0.7122, 1.2943, 0.8282, 1.3215, 0.9727, 0.8254,…
NotePourquoi l’affichage est-il tronqué dans le livre ?

glimpse(enquete_csv) reste la commande à utiliser sur votre machine. Le livre n’imprime ici qu’un sous-ensemble de colonnes représentatives afin d’éviter plusieurs dizaines de lignes de console qui n’ajouteraient pas d’information pédagogique.

Nous attendons 3 600 lignes et 36 colonnes. Si le résultat est 3600 × 1, le séparateur est probablement mal interprété.

4.5.2 Lire explicitement un séparateur

enquete <- read_delim(
  "data/raw/fichier.txt",
  delim = ";"
)

Ne pas choisir ; parce que « les fichiers français utilisent des points-virgules » : ouvrir quelques lignes du fichier ou consulter sa documentation permet de vérifier le délimiteur réel.

4.5.3 Examiner les problèmes de parsing

readr essaie d’inférer le type des colonnes. En cas de problème, on peut inspecter :

problems(enquete_csv)

Cette fonction est particulièrement utile lorsqu’une valeur ne correspond pas au type attendu.

4.5.4 Forcer un type lorsque la documentation le justifie

Supposons qu’un identifiant comme 00125 doive conserver ses zéros initiaux :

read_csv(
  "fichier.csv",
  col_types = cols(
    identifiant = col_character()
  )
)

La décision doit venir de la signification de la variable, pas uniquement de l’apparence des premières lignes.

AvertissementLes identifiants ne sont pas des quantités

Un code postal, un numéro de ménage ou un identifiant peuvent être composés de chiffres sans être des variables quantitatives. Les convertir automatiquement en nombres peut supprimer des zéros initiaux ou encourager des calculs sans sens.

4.6 Les valeurs manquantes au moment de l’import

On peut indiquer à read_csv() certaines chaînes représentant des valeurs manquantes :

read_csv(
  "fichier.csv",
  na = c("", "NA")
)

Mais il faut être prudent avec les codes spéciaux d’enquête. Dans enqueter_brut.csv, 98, 99, 99997 ou 99998 peuvent avoir des significations comme « ne sait pas », « refus » ou « sans objet ». Les convertir tous en NA dès l’import peut faire disparaître une information utile sur la nature de la non-réponse.

Dans ce livre, nous préférons donc :

  1. importer les codes tels qu’ils figurent dans le fichier brut ;
  2. consulter le questionnaire et le dictionnaire ;
  3. créer ensuite un traitement explicite et documenté.

Cette séparation est essentielle pour le chapitre 7.

4.7 Excel avec {readxl}

{readxl} lit les fichiers .xls et .xlsx (Wickham et Bryan 2026).

Afficher le code R
library(readxl)

enquete_excel <- read_excel(
  "data/raw/enqueter_brut.xlsx",
  sheet = "enqueter_brut"
)

Pour connaître les feuilles :

excel_sheets("data/raw/enqueter_brut.xlsx")

4.7.1 Excel : pratique, mais pas neutre

Une feuille Excel peut contenir :

  • des cellules vides ;
  • des colonnes mêlant nombres et texte ;
  • des dates encodées de manière particulière ;
  • des formules ;
  • plusieurs lignes d’en-tête ;
  • des commentaires ou tableaux situés au-dessus des données.

L’import peut donc nécessiter skip, range ou col_types.

read_excel(
  "fichier.xlsx",
  sheet = 2,
  skip = 3
)

Dans le kit EnquêteR, le fichier Excel est volontairement simple : une feuille rectangulaire contenant les mêmes valeurs que le CSV. Il sert à apprendre la mécanique d’importation, pas à reproduire toutes les difficultés possibles d’un classeur réel.

4.8 Stata, SPSS et SAS avec {haven}

Le package {haven} lit les principaux formats utilisés par les logiciels statistiques :

haven::read_dta("fichier.dta")
haven::read_sav("fichier.sav")
haven::read_sas("fichier.sas7bdat")

Ces formats ont un avantage important pour les données d’enquête : ils peuvent conserver des métadonnées qui ne sont pas présentes dans un CSV (Wickham, Miller, et al. 2026).

4.8.1 Importer le fichier Stata fourni

Afficher le code R
library(haven)

enquete_dta <- read_dta("data/raw/enqueter_brut.dta")

dim(enquete_dta)
[1] 3600   36
Afficher le code R
enquete_dta |>
  dplyr::select(id, age, sexe, diplome, revenu_mensuel, poids_final) |>
  dplyr::glimpse()
Rows: 3,600
Columns: 6
$ id             <dbl> 100001, 100002, 100003, 100004, 100005, 100006, 100007,…
$ age            <dbl> 70, 64, 30, 41, 48, 63, 51, 27, 35, 34, 31, 28, 33, 54,…
$ sexe           <dbl+lbl> 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, …
$ diplome        <dbl+lbl> 2, 2, 3, 7, 1, 2, 7, 7, 5, 3, 1, 2, 5, 3, 2, 4, 1, …
$ revenu_mensuel <dbl> 1750, 99997, 2600, 3400, 2200, 2350, 99998, 2500, 2350,…
$ poids_final    <dbl> 1.2692, 0.7122, 1.2943, 0.8282, 1.3215, 0.9727, 0.8254,…

Certaines colonnes apparaissent avec une classe de type haven_labelled. Ce n’est pas une erreur. Cela signifie que la variable peut contenir une valeur numérique et, en parallèle, un dictionnaire associant certains codes à des libellés.

4.8.2 Voir les labels d’une variable

Afficher le code R
attributes(enquete_dta$diplome)
$label
[1] "Plus haut diplôme obtenu"

$format.stata
[1] "%12.0g"

$class
[1] "haven_labelled" "vctrs_vctr"     "double"        

$labels
   Sans diplôme      Brevet/CEP         CAP/BEP    Baccalauréat           Bac+2 
              1               2               3               4               5 
        Licence Master/doctorat     Ne sait pas           Refus 
              6               7              98              99 

ou, avec {labelled} :

Afficher le code R
labelled::val_labels(enquete_dta$diplome)
   Sans diplôme      Brevet/CEP         CAP/BEP    Baccalauréat           Bac+2 
              1               2               3               4               5 
        Licence Master/doctorat     Ne sait pas           Refus 
              6               7              98              99 
Afficher le code R
labelled::var_label(enquete_dta$diplome)
[1] "Plus haut diplôme obtenu"

Nous n’allons pas convertir immédiatement cette variable en facteur. Le chapitre suivant explique pourquoi.

4.8.3 Générer et importer la version SPSS

Si le fichier .sav n’est pas déjà présent, le script fourni peut le produire :

source("R/00-creer-fichiers-riches.R")

Puis :

Afficher le code R
if (file.exists("data/raw/enqueter_brut.sav")) {
  enquete_spss <- haven::read_sav("data/raw/enqueter_brut.sav")
  enquete_spss |>
    dplyr::select(id, age, sexe, diplome, revenu_mensuel, poids_final) |>
    dplyr::glimpse()
}
Rows: 3,600
Columns: 6
$ id             <dbl> 100001, 100002, 100003, 100004, 100005, 100006, 100007,…
$ age            <dbl> 70, 64, 30, 41, 48, 63, 51, 27, 35, 34, 31, 28, 33, 54,…
$ sexe           <dbl+lbl> 2, 1, 2, 2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 2, 1, …
$ diplome        <dbl+lbl> 2, 2, 3, 7, 1, 2, 7, 7, 5, 3, 1, 2, 5, 3, 2, 4, 1, …
$ revenu_mensuel <dbl> 1750, 99997, 2600, 3400, 2200, 2350, 99998, 2500, 2350,…
$ poids_final    <dbl> 1.2692, 0.7122, 1.2943, 0.8282, 1.3215, 0.9727, 0.8254,…

L’utilisation de if (file.exists(...)) évite qu’un rendu Quarto échoue uniquement parce qu’un format optionnel n’a pas été généré.

4.9 Une même enquête, des objets qui ne sont pas identiques

Comparons conceptuellement :

Afficher le code R
class(enquete_csv$diplome)
[1] "numeric"
Afficher le code R
class(enquete_excel$diplome)
[1] "character"
Afficher le code R
class(enquete_dta$diplome)
[1] "haven_labelled" "vctrs_vctr"     "double"        

Les valeurs visibles peuvent être les mêmes, mais le fichier Stata peut porter des labels supplémentaires. Cette différence devient importante lorsque l’on produit des tableaux ou que l’on doit distinguer un code 98 d’une modalité substantive.

4.9.1 Comparer les dimensions

Afficher le code R
rbind(
  CSV   = c(nrow(enquete_csv), ncol(enquete_csv)),
  Excel = c(nrow(enquete_excel), ncol(enquete_excel)),
  Stata = c(nrow(enquete_dta), ncol(enquete_dta))
)
      [,1] [,2]
CSV   3600   36
Excel 3600   36
Stata 3600   36

Les dimensions sont un premier contrôle, pas une preuve d’identité.

4.9.2 Comparer des valeurs

Pour une sélection de colonnes :

Afficher le code R
head(enquete_csv[c("id", "age", "diplome")])
# A tibble: 6 × 3
      id   age diplome
   <dbl> <dbl>   <dbl>
1 100001    70       2
2 100002    64       2
3 100003    30       3
4 100004    41       7
5 100005    48       1
6 100006    63       2
Afficher le code R
head(enquete_dta[c("id", "age", "diplome")])
# A tibble: 6 × 3
      id   age diplome            
   <dbl> <dbl> <dbl+lbl>          
1 100001    70 2 [Brevet/CEP]     
2 100002    64 2 [Brevet/CEP]     
3 100003    30 3 [CAP/BEP]        
4 100004    41 7 [Master/doctorat]
5 100005    48 1 [Sans diplôme]   
6 100006    63 2 [Brevet/CEP]     

Pour une comparaison plus rigoureuse, il peut être nécessaire de retirer temporairement les attributs de labels ou d’harmoniser les classes. Le but ici est de comprendre qu’égalité des valeurs et égalité des métadonnées sont deux questions distinctes.

4.10 Encodage des caractères

Un fichier texte peut avoir été enregistré avec différents encodages. Un symptôme classique est l’affichage incorrect des caractères accentués.

Avec readr, on peut préciser une locale :

read_csv(
  "fichier.csv",
  locale = locale(encoding = "Latin1")
)

Il ne faut pas choisir un encodage au hasard jusqu’à ce que le résultat « ait l’air correct ». Idéalement, l’encodage est documenté ou identifié à partir du fichier source.

UTF-8 est aujourd’hui un choix courant et robuste pour les projets reproductibles, mais les fichiers historiques d’enquête peuvent utiliser d’autres encodages.

4.11 Nombres, virgules et séparateurs

Un fichier peut contenir :

revenu;score
1 850,50;7,2
2 100,00;8,0

Dans ce cas, read_csv() n’est pas adapté. read_csv2() utilise les conventions ; et , prévues pour ce type de fichier :

read_csv2("fichier.csv")

On peut aussi expliciter la locale pour des cas particuliers.

AstuceDiagnostic rapide

Lorsque l’import semble incohérent, vérifier d’abord quatre choses : séparateur, décimales, encodage, ligne d’en-tête. Ce sont des causes fréquentes avant même de considérer un problème statistique.

4.12 Le contrôle post-import : une routine obligatoire

Après chaque importation, nous allons adopter la même routine.

4.12.1 Dimensions

dim(enquete)

Sont-elles compatibles avec la documentation ?

4.12.2 Noms

names(enquete)

Les variables attendues sont-elles présentes ? Y a-t-il des colonnes vides ou des noms modifiés ?

4.12.3 Structure

dplyr::glimpse(enquete)

Les identifiants, dates, variables catégorielles et quantitatives semblent-ils avoir des classes plausibles ?

4.12.4 Premières et dernières lignes

head(enquete)
tail(enquete)

Cette inspection ne remplace pas un diagnostic, mais permet de détecter une ligne d’en-tête répétée, une note importée comme observation ou une ligne vide inhabituelle.

4.12.5 Valeurs distinctes sur quelques variables clés

unique(enquete$sexe)
unique(enquete$diplome)

Sur un fichier très volumineux, dplyr::count() est souvent plus utile :

dplyr::count(enquete, sexe)

4.12.6 Problèmes signalés par le lecteur

Pour readr :

problems(enquete_csv)

Cette routine doit devenir automatique. Elle est l’équivalent d’un contrôle de réception avant de commencer le nettoyage.

4.13 Ne pas renommer l’objet data

R possède déjà une fonction data(). Écrire :

data <- read_csv("...")

fonctionne souvent, mais crée une ambiguïté inutile. Des noms comme enquete_brute, enquete_csv ou donnees_importees sont plus informatifs.

4.14 Une stratégie d’importation reproductible

Un script R/01-import.R peut contenir :

library(readr)
library(dplyr)

# Import ----
enquete_brute <- read_csv(
  "data/raw/enqueter_brut.csv",
  show_col_types = FALSE
)

# Contrôles minimaux ----
stopifnot(nrow(enquete_brute) == 3600)
stopifnot(ncol(enquete_brute) == 36)
stopifnot("id" %in% names(enquete_brute))

glimpse(enquete_brute)

stopifnot() interrompt le script si une hypothèse essentielle est fausse. C’est utile lorsque l’on sait précisément ce que le fichier doit contenir.

NotePourquoi contrôler une dimension connue ?

Si une nouvelle livraison contient 3 599 lignes au lieu de 3 600, le script doit attirer l’attention. Continuer silencieusement peut propager une erreur jusqu’aux résultats finaux.

4.15 Mini-exercices

4.15.1 Mini-exercice 1 — Choisir la fonction

Associez chaque fichier à une fonction :

  • enquete.csv séparé par virgules ;
  • enquete_europe.csv séparé par ; avec virgule décimale ;
  • enquete.xlsx feuille repondants ;
  • enquete.dta ;
  • enquete.sav.
readr::read_csv("enquete.csv")
readr::read_csv2("enquete_europe.csv")
readxl::read_excel("enquete.xlsx", sheet = "repondants")
haven::read_dta("enquete.dta")
haven::read_sav("enquete.sav")

4.15.2 Mini-exercice 2 — Diagnostiquer un import à une colonne

Vous attendiez 40 variables mais dim(enquete) retourne 2500 1. Quelle hypothèse testez-vous en premier ?

Le séparateur est l’une des premières hypothèses. Ouvrir quelques lignes du fichier comme texte permet de voir s’il est séparé par ;, tabulation ou un autre caractère. Ensuite utiliser read_csv2(), read_tsv() ou read_delim() selon le cas.

4.16 Exercice de synthèse

4.16.1 Niveau A — Appliquer

Importez enqueter_brut.csv. Vérifiez :

  1. le nombre de lignes et colonnes ;
  2. les dix premiers noms de variables ;
  3. la structure de age, diplome et revenu_mensuel ;
  4. les fréquences brutes de sexe ;
  5. l’existence de problèmes de parsing.

4.16.2 Niveau B — Choisir

Importez le CSV et le fichier Stata. Comparez diplome dans les deux objets. Expliquez pourquoi les deux colonnes peuvent porter les mêmes codes tout en n’étant pas des objets R strictement équivalents.

4.16.3 Niveau C — Analyser

Rédigez un script d’importation qui :

  • importe le fichier principal ;
  • vérifie automatiquement que id, age, poids_final et diplome sont présents ;
  • interrompt l’exécution si le fichier contient moins de 3 000 lignes ;
  • affiche un résumé compact de la structure ;
  • ne transforme aucune valeur du fichier brut.

Expliquez en cinq lignes pourquoi vous séparez l’importation du nettoyage.

4.16.4 Correction détaillée

library(readr)
library(dplyr)

enquete <- read_csv(
  "data/raw/enqueter_brut.csv",
  show_col_types = FALSE
)

dim(enquete)
head(names(enquete), 10)
class(enquete$age)
class(enquete$diplome)
class(enquete$revenu_mensuel)
count(enquete, sexe)
problems(enquete)

Le fichier attendu comporte 3 600 lignes et 36 colonnes. Le fait que diplome soit numérique dans le CSV ne permet pas de conclure qu’il s’agit d’une variable quantitative.

library(readr)
library(dplyr)

enquete_brute <- read_csv(
  "data/raw/enqueter_brut.csv",
  show_col_types = FALSE
)

variables_obligatoires <- c("id", "age", "poids_final", "diplome")

stopifnot(all(variables_obligatoires %in% names(enquete_brute)))
stopifnot(nrow(enquete_brute) >= 3000)

glimpse(enquete_brute)

La séparation est méthodologique : l’import doit permettre de reproduire fidèlement la lecture du fichier source. Les règles de nettoyage dépendent ensuite du questionnaire, des codes et des incohérences identifiées. Les mélanger rend plus difficile de savoir si une valeur a disparu au moment de la lecture ou lors d’une transformation analytique.

4.17 À retenir

  • Le choix de la fonction d’import dépend du format réel du fichier.
  • CSV et Excel transportent généralement moins de métadonnées analytiques que SPSS, Stata ou SAS.
  • {haven} conserve les labels dans des objets haven_labelled lorsque le format source les contient (Wickham, Miller, et al. 2026).
  • Les codes spéciaux d’enquête ne doivent pas être transformés aveuglément en NA dès l’import.
  • Après chaque import : dimensions, noms, structure, premières lignes et valeurs clés doivent être contrôlés.
  • Un import réussi techniquement peut être incorrect méthodologiquement.
  • Les fichiers bruts restent inchangés ; les transformations viennent ensuite.

4.18 Pour aller plus loin

La documentation de {readr} décrit les lecteurs de fichiers délimités et le diagnostic des problèmes de parsing (Wickham, Hester, et al. 2026). {readxl} fournit les fonctions d’import Excel (Wickham et Bryan 2026), tandis que {haven} assure l’interface avec SPSS, Stata et SAS (Wickham, Miller, et al. 2026).

Le chapitre suivant porte sur une difficulté spécifique aux données d’enquête : comprendre ce que signifient réellement les colonnes que nous venons d’importer, leurs labels, leurs codes et leur type conceptuel.