13. Importer des données

Jusqu’à présent, les données utilisées provenaient de datasets inclus dans les packages R et Tidyverse ou étaient fournies sous la forme d’objets R.

On va explorer l’utilisation de données externes fournies sous la forme de fichiers.

La lecture de données est relativement simple en R, à la condition express que les données soient strictement structurées. La bonne pratique est d’effectuer le travail de nettoyage et de formattage avec un autre langage, avant d’importer les données dans R. Python est un excellent candidat pour ces tâches.

Par la suite, les données importées seront structurées pour que l’importation se passe sans difficulté.

On va se contenter ici d’importer les données à partir de fichiers texte, mais on trouvera un document exhaustif sur l’importation de données dans le R Data Import/Export manual. Ca couvre également la connexion à des bases de données.

13.1. La fonction read.table()

La fonction utilisée pour l’importation de données à partir de fichiers texte est read.table() dont on trouvera la documentation complète ici.

Pour une importation réussie des données dans une data.frame, le fichier texte doit avoir un format bien défini:

  • la première ligne du fichier doit contenir le nom de chaque variable de la data.frame.

  • chaque ligne supplémentaire doit être composée d’un label et de valeurs pour chacune des variables décrites sur la première ligne.

Si cette structuration n’est pas respectée, l’importation peut conduire à des erreurs où à des résultats imprévisibles.

A titre d’exemple, un fichier bien formé ressemble à ça:

     Price    Floor     Area   Rooms     Age  Cent.heat
01   52.00    111.0      830     5       6.2      no
02   54.75    128.0      710     5       7.5      no
03   57.50    101.0     1000     5       4.2      no
04   57.50    131.0      690     6       8.8      no
05   59.75     93.0      900     5       1.9     yes
...

Le comportement par défaut est que les valeurs numériques sont importées avec le mode numeric et que les variables non numériques sont importées comme des factors (Cent.heat dans cet exemple). Ce comportement par défaut peur être modifié en passant des paramètres à la fonction read.table().

Le fichier utilisé pour cet exemple se trouve ici.

En utilisant les paramètres par défaut, on construit la data.frame de la façon suivante:

> HousePrice <- read.table("houses.data")

La data.frame importée:

> HousePrice
   Price Floor Area Rooms Age Cent.heat
01 52.00   111  830     5 6.2        no
02 54.75   128  710     5 7.5        no
03 57.50   101 1000     5 4.2        no
04 57.50   131  690     6 8.8        no
05 59.75    93  900     5 1.9       yes

Il peut arriver que les observations contenues dans le fichier ne disposent pas de labels. Dans ce cas, les labels sont construits à la volée en utilisant les entiers consécutifs.

Un fichier sans label pour les observations ressemble à ça:

Price    Floor     Area   Rooms     Age  Cent.heat
52.00    111.0      830     5       6.2      no
54.75    128.0      710     5       7.5      no
57.50    101.0     1000     5       4.2      no
57.50    131.0      690     6       8.8      no
59.75     93.0      900     5       1.9     yes
...

On le lit (presque) de la même façon:

> HousePrice <- read.table("houses.data", header=TRUE)

Il faut cependant spécifier le paramètre header=TRUE pour indiquer que la première ligne contient le nom des variables (c’était implicite précédemment car les lignes d’observations étaient identifiées par un label).

La data.frame importée:

> HousePrice
  Price Floor Area Rooms Age Cent.heat
1 52.00   111  830     5 6.2        no
2 54.75   128  710     5 7.5        no
3 57.50   101 1000     5 4.2        no
4 57.50   131  690     6 8.8        no
5 59.75    93  900     5 1.9       yes

Exercice

Utiliser le fichier films.csv pour construire la data.frame utilisée dans le chapitre Facteurs. Vérifier attentivement le résultat de la lecture avec la fonction View(). Utiliser les paramètres de la fonction read.table() pour corriger d’éventuels dysfonctionnements.

13.2. Les datasets fournis

Comme indiqué précedemment, R et Tidyverse fournissent un grand nombre de datasets fort utiles pour prendre en main et tester les fonctions de manipulation de données.

Pour obtenir une liste de ces datasets:

> data(package = .packages(all.available = TRUE))

Chacun de ces dataset est accessible par son nom (le package doit avoir été importé).

On peut à titre d’exemple s’intéresser aux datasets Freedman, storms, starwars, gapminder, lakers, etc…

> library(lubridate)

Attaching package:lubridateThe following objects are masked frompackage:base:

    date, intersect, setdiff, union

> summary(lakers)

13.3. Application : le Titanic

On va s’intéresser aux données relatives au naufrage du Titanic qui s’est déroulé dans la matinée du 15 avril 1912. On trouvera une source complète d’informations sur l”Encyclopedia Titanica.

Créer un nouveau projet dans RStudio. Télécharger les données stockées au format texte (csv).

13.3.1. Importation

Observer la structure du fichier dans un éditeur de texte. Utiliser la fonction read.table() pour lire les données.

  • Quel est la nature de l’objet retourné par cette opération ?

  • Utiliser la fonction as_tibble() pour transformer l’objet

  • Afficher le tibble ainsi construit

  • Comment peut on afficher les 20 premières lignes ?

  • Quelles sont les informations présentes ?

  • Sont elles toutes renseignées ?

  • Utiliser les fonctions summary() et str() pour avoir un aperçu des données.

  • Quel est le type de chacune des variables ?

  • Construire une table de contingence pour chacune des variable. Quelles premières conclusions en tirer ?

13.3.2. Nettoyage des données

Il est fréquent qu’un jeu de données contienne des valeurs manquantes. Une opération courante est d’isoler les observations complètes (aucune valeur manquante) ou au contraire les observations incomplètes (au moins une valeur manquante).

Astuce

S’il subsiste des valeurs manquantes dans un vecteur numérique, les fonctions courantes (mean() par exemple) ont un paramètre na.rm qui permet d’écarter ces valeurs au moment du traitement.

Rechercher les observations incomplètes (avec au moins une valeur manquante) avec la fonction filter() à laquelle on passera un vecteur logique construit à partir des fonctions if_any() ou if_all(), auxquelles on passera deux paramètres:

  • un opérateur de sélection de variable (la fonction everything() les prend toutes)

  • la fonction à appliquer à chacune des variables sélectionnées

Le vecteur logique ainsi crée sera réduit par if_any() ou if_all() à une seule valeur logique.

On consultera:

  • la documentation de la fonction filter

  • la documentation de la fonction if_any

On doit obtenir:

    Age Class.Dept Fare.today      Joined Survived
1    39  1st Class         NA     Belfast    FALSE
2    NA  1st Class       2390 Southampton    FALSE
3    40  1st Class         NA     Belfast    FALSE

...

198  26     Engine         NA     Belfast    FALSE
199  36     Engine         NA Southampton    FALSE
200  29     Engine         NA Southampton    FALSE
 [ reached 'max' / getOption("max.print") -- omitted 723 rows ]

Identifier les trois premières observations de ces observations incomplètes.

On doit obtenir:

  Age Class.Dept Fare.today      Joined Survived
1  39  1st Class         NA     Belfast    FALSE
2  NA  1st Class       2390 Southampton    FALSE
3  40  1st Class         NA     Belfast    FALSE

L’index de la data.frame initiale a été perdu dans l’opération. Pour le conserver, il faut explicitement en faire une variable avant le filtrage avec la fonction tibble::rownames_to_column().

On doit obtenir:

  index Age Class.Dept Fare.today      Joined Survived
1     8  39  1st Class         NA     Belfast    FALSE
2    47  NA  1st Class       2390 Southampton    FALSE
3    71  40  1st Class         NA     Belfast    FALSE

La fonction réciproque tibble::column_to_rownames() permet de supprimer la variable temporaire et de renommer les observations avec l’index de la data.frame originale.

On doit obtenir:

   Age Class.Dept Fare.today      Joined Survived
8   39  1st Class         NA     Belfast    FALSE
47  NA  1st Class       2390 Southampton    FALSE
71  40  1st Class         NA     Belfast    FALSE

De la même façon, rechercher les observations complètes, sans aucune valeur manquante. Combien y en a t-il ?

On doit obtenir:

     Age Class.Dept Fare.today      Joined Survived
1   29.0  1st Class      16300 Southampton     TRUE
2    0.9  1st Class      11700 Southampton     TRUE
3    2.0  1st Class      11700 Southampton    FALSE

...

206 25.0  1st Class       6350   Cherbourg     TRUE
207 65.0  1st Class       2050   Cherbourg    FALSE
208 44.0  1st Class       6950                FALSE
 [ reached 'max' / getOption("max.print") -- omitted 1085 rows ]

On peut cibler une (ou plusieurs) variable particulière en la passant en argument de if_any() ou if_all().

Rechercher les trois premières observations pour lesquelles l’age est manquant.

On doit obtenir:

    Age Class.Dept Fare.today      Joined Survived
47   NA  1st Class       2390 Southampton    FALSE
180  NA  1st Class       2050 Southampton    FALSE
182  NA  1st Class       2700 Southampton    FALSE

Astuce

Lorsque peu de variables sont en jeu, on peut construire le vecteur logique plus simplement :

> df %>% filter(is.na(Age))

La valeur ajoutée de if_any() ou if_all() réside dans leur capacité à réduire un grand nombre de valeurs logiques.

Rechercher les observations pour lesquelles l’age et le montant du billet sont manquant.

On doit obtenir:

     Age  Class.Dept Fare.today      Joined Survived
1516  NA      Engine         NA Southampton    FALSE
1737  NA      Engine         NA Southampton    FALSE
2153  NA Victualling         NA Southampton    FALSE

Observer les lignes 208, 209 et 210 de la data.frame originale. Ces lignes sont elles identifiées comme des observations incomplètes ? Pour répondre à cette question:

  • construire la data.frame inc_obs des observations incomplètes.

  • créer une variable index à partir du nom des observations avec la fonction tibble::rownames_to_column(). Quel est le type de la variable index ?

  • convertir la variable index en un type propice à la comparaison avec la fonction mutate() à laquelle on passe la fonction across() en argument

  • filtrer selon les observations recherchées

Construire l’instruction complète permettant de s’assurer que les lignes 208, 209 et 210 sont, ou ne sont pas dans les observations incomplètes.

Quelle opération doit on réaliser pour ajouter ce genre d’observation aux observations incomplètes ? Par défaut R affecte la valeur NA aux variables numériques et logiques pour lesquelles il détecte un champ vide, mais pas aux variables de type character.

Utiliser la fonction mutate() à laquelle on passe la across() qui prend deux arguments:

  • les colonnes sélectionnées par position, par nom ou par type avec la fonction where()

  • la fonction na_if() à appliquer, précédée d’un tilde ~ pour en faire une formule. . représente la variable sélectionnée.

Pour chacune de ces fonctions, on lira attentivement la documentation et les exemples associés.

Astuce

Il est souvent plus efficace de convertir en NA les valeurs concernées lors du processus de lecture. N’y a t’il pas un paramètre de la fonction read.table() qui permet de considérer les chaines de caractères vides comme des valeurs NA ?

Construire la data.frame com_obs des observations complètes.

Observer le résultat de la fonction summary() appliquée à la data.frame des observations complètes. On doit obtenir:

     Age            Class.Dept    Fare.today            Joined
Min.   : 0.00   1st Class:310   Min.   :  245   Belfast    :  1
1st Qu.:21.00   2nd Class:271   1st Qu.:  612   Cherbourg  :271
Median :27.00   3rd Class:701   Median : 1120   Queenstown :120
Mean   :29.32                   Mean   : 2600   Southampton:890
3rd Qu.:37.00                   3rd Qu.: 2420
Max.   :74.00                   Max.   :39600
 Survived
Mode :logical
FALSE:786
TRUE :496

Est ce le cas ? Si non, quelles variables ne produisent pas les résultats escomptés.

Comment peut on les modifier avec la fonction mutate() ?

Astuce

Il existe un paramètre de la fonction read.table() permettant de convertir les chaines de caractères en facteurs à la lecture des données.

13.3.3. Transformation

L”Encyclopedia Titanica fournit un certain nombre de statistiques. Nous allons en reconstruire certaines en utilisant R et Tidyverse. Il peut y avoir des écarts mineurs dans les résultats obtenus, dus à des écarts entre les jeux de données utilisés.

On va utiliser les data.frame précédentes pour tirer quelques conclusions et tracer quelques graphiques.

Combien de passagers ont embarqué à Belfast, Southampton ? Cherbourg ? Queenstown ?

Construire la figure ci dessous.

_images/fr-13-joined.png

Astuce

La variable Joined contient des NA. On peut les éliminer en amont en construisant une data.frame spécifique. On peut également les écarter au moment de la construction de l’objet ggplot avec la fonction remove_missing() : la documentation.

Tracer l’histogramme de l’age des passagers pour chaque classe.

_images/11-files-fig01.png

Astuce

On peut utiliser la fonction stringr::str_detect() pour sélectionner les observations de la variable Class.Dept

Quels sont la moyenne, la médiane, et l’écart type le la variable Age pour chacune des valeurs de la variable Class.Dept ? On utilisera la fonction group_by() pour le groupement de données et la fonction summarize() pour la réduction.

Construire le flux de traitement conduisant à

# A tibble: 7 x 2
  Class.Dept   mean
  <fct>       <dbl>
1 1st Class    39.1
2 2nd Class    29.6
3 3rd Class    24.9
4 A la Carte   25.2
5 Deck         33.8
6 Engine       30.4
7 Victualling  31.3

Astuce

Les fonctions statistiques comme mean() acceptent le paramètre na.rm=TRUE pour écarter les NA avant traitement.

Modifier le groupement pour obtenir l’age moyen des survivants et des morts pour chacune des classes.

Compter le nombre de morts et de survivants pour chacune des classes de passagers et comparer les pourcentages.

# A tibble: 14 x 4
# Groups:   Class.Dept [7]
   Class.Dept  Survived     n percent
   <fct>       <lgl>    <int>   <dbl>
 1 1st Class   FALSE      123  0.380
 2 1st Class   TRUE       201  0.620
 3 2nd Class   FALSE      166  0.582
 4 2nd Class   TRUE       119  0.418
 5 3rd Class   FALSE      528  0.746
 6 3rd Class   TRUE       180  0.254
 7 A la Carte  FALSE       66  0.957
 8 A la Carte  TRUE         3  0.0435
 9 Deck        FALSE       23  0.348
10 Deck        TRUE        43  0.652
11 Engine      FALSE      253  0.778
12 Engine      TRUE        72  0.222
13 Victualling FALSE      337  0.782
14 Victualling TRUE        94  0.218
    Source: local data frame [14 x 4]
    Groups: Class.Dept [7]

A partir du tibble précédent, tracer un barplot pour mettre en évidence le pourcentage de survivant pour chacune des catégories de passagers.

_images/11-files-fig02.png