11. Data frames

La data.frame est la structure de plus haut niveau en R et s’apparente à l’onglet d’un tableur. Les structures de données présentées jusqu’alors (vecteurs, arrays, matrices, listes) sont des structures de plus bas niveau, dont il faut connaître le fonctionnement, mais la plupart du temps, dans une utilisation moderne de R, on travaillera avec des data frames.

Dans le chapitre Un aperçu du fonctionnement de R, on a vu que c’était la structure de données de base utilisé par le package graphique ggplot2. Dans ce chapitre, on va s’intéresser à la façon de construire des data frames et aux différentes fonctions qui permettent de les manipuler.

D’un point de vue fonctionnement interne de R, en fait une data.frame est simplement une list de classe data.frame.

Une data.frame possède les caractéristiques suivantes:

  • les éléments doivent être des vecteurs (numeric, character, ou logical), des facteurs, des matrices, des listes, ou d’autres data frames.

  • les matrices, les listes, et les data frames fournissent autant de variables (colonnes) qu’il y a de colonnes dans les matrices, d’éléments dans les listes ou de variables dans les data frames.

  • les vecteurs numériques ou logique, et les facteurs sont intégrés tels quels. Les vecteurs de chaîne de caractères sont convertis en facteurs.

  • les vecteurs utilisés pour la construction de la data.frame doivent avoir la même longueur.

  • les matrices utilisées pour la construction de la data.frame doivent avoir le même nombre de lignes.

  • les colonnes et les lignes d’une data.frame peut être extraits suivant la syntaxe utilisée pour les matrices.

11.1. Construire une data frame

Une data.frame peut être construite à partir de vecteurs (satisfaisant les conditions ci dessus) avec la fonction data.frame():

> characters <- c("Walter White", "Jesse Pinkman", "Saul Goodman")
> birthdates <- as.Date(c("1959-09-07","1984-9-14",NA))
> occupations <- c("chemist", "meth manufacturer", "criminal lawyer")
> df <- data.frame(characters, birthdates, occupations)
> df
     characters birthdates       occupations
1  Walter White 1959-09-07           chemist
2 Jesse Pinkman 1984-09-14 meth manufacturer
3  Saul Goodman       <NA>   criminal lawyer

Astuce

Une liste dont les éléments satisfont les conventions ci dessus peut être transformée en data.frame avec la fonction as.data.frame().

Dans la plupart des cas, on construira les data frames à partir de données structurées stockées dans un fichier avec la fonction read.table(). On abordera cette façon de faire dans le chapitre Importer des données.

11.2. Utiliser une data.frame

R fournit plusieurs datasets de base permettant de disposer de data frames sans lecture de données externes:

> library(help = "datasets")

Chaque dataset contient des informations descriptives permettant de connaitre la structure des données:

> help(iris)

Le chargement d’un dataset en mémoire est très simple. Utilisons ici le célèbre dataset des iris d’Edgar Anderson:

> data(iris)
> typeof(iris)
[1] "list"
> class(iris)
[1] "data.frame"
_images/09-dataframes-fig01.jpg

Examinons les données brutes dans la console:

> iris
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
1            5.1         3.5          1.4         0.2     setosa
2            4.9         3.0          1.4         0.2     setosa
3            4.7         3.2          1.3         0.2     setosa
4            4.6         3.1          1.5         0.2     setosa
5            5.0         3.6          1.4         0.2     setosa
...          ...         ...          ...         ...      ...
146          6.7         3.0          5.2         2.3  virginica
147          6.3         2.5          5.0         1.9  virginica
148          6.5         3.0          5.2         2.0  virginica
149          6.2         3.4          5.4         2.3  virginica
150          5.9         3.0          5.1         1.8  virginica

On a ainsi un premier aperçu des variables (colonnes) disponibles : la longueur et la largeur des sépales, la longueur et la largeur des pétales, et la variété de la fleur.

11.2.1. Caractéristiques

Les fonctions dim(), colnames() et rownames() permettent de connaitre respectivement la taille, le nom des variables (colonnes) et le nom des observations (lignes). Pour cette dernière caractéristique, le nom est très souvent le numéro de l’observation. On dit aussi l’index:

> dim(iris)
[1] 150   5

> colnames(iris)
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"

> rownames(iris)
  [1] "1"   "2"   "3"   "4"   "5"   "6"   ...

On peut extraire les premières lignes:

> head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Ou bien les dernières:

> tail(iris)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
145          6.7         3.3          5.7         2.5 virginica
146          6.7         3.0          5.2         2.3 virginica
147          6.3         2.5          5.0         1.9 virginica
148          6.5         3.0          5.2         2.0 virginica
149          6.2         3.4          5.4         2.3 virginica
150          5.9         3.0          5.1         1.8 virginica

On peut passer un deuxième argument aux fonctions head() et tail() pour indiquer le nombre de ligne souhaitées (6 par défaut). On peut également utiliser un deuxième argument négatif:

> head(iris, -144) # tout sauf les 144 dernières lignes, équivalent à head(iris, 6) ou head(iris)
    Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

> tail(iris, -144)) # tout sauf les 144 premières lignes, équivalent à tail(iris, 6) ou tail(iris)
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
145          6.7         3.3          5.7         2.5 virginica
146          6.7         3.0          5.2         2.3 virginica
147          6.3         2.5          5.0         1.9 virginica
148          6.5         3.0          5.2         2.0 virginica
149          6.2         3.4          5.4         2.3 virginica
150          5.9         3.0          5.1         1.8 virginica

On obtient une vue synthétique de la data.frame avec la fonction str():

> str(iris)
'data.frame':       150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

et une description statistique avec la fonction summary():

> summary(iris)
  Sepal.Length    Sepal.Width     Petal.Length    Petal.Width          Species
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :50
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:50
 Median :5.800   Median :3.000   Median :4.350   Median :1.300   virginica :50
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500

Important

Pour les variables (colonnes) numériques, summary() utilise les résultats de la fonction quantile(). Un rafraichissement sur ce que sont des quantiles sur la page Wikipedia.

On peut également obtenir une vue plus structurée avec la fonction View() qui met en avance la similitude avec une feuille de tableur:

> View(iris)
_images/09-dataframes-fig02.png

11.2.2. Ajouter des données

Une data.frame est une structure de type matricielle et il est possible d’ajouter des lignes et des colonnes avec des fonctions « bas niveau » de type cbind() et rbind(). C’est une bonne pratique de pratiquer une coercition explicite avec la fonction as.data.frame():

> gender <- rep("male",3)
> edf <- cbind(df, as.data.frame(gender))
> edf
     characters birthdates       occupations gender
1  Walter White 1959-09-07           chemist   male
2 Jesse Pinkman 1984-09-14 meth manufacturer   male
3  Saul Goodman       <NA>   criminal lawyer   male

L’utilisation de la fonction rbind() nécessite de formatter les lignes à ajouter dans une data.frame avec des noms de variables (colonnes) identiques:

> sw <- list("Skyler White", as.Date(c("1970-08-11")), "car wash manager", "female")
> names(sw) <- names(edf)
> str(sw)
List of 4
 $ characters : chr "Skyler White"
 $ birthdates : Date[1:1], format: "1970-08-11"
 $ occupations: chr "car wash manager"
 $ gender     : chr "female"

> edf2 <- rbind(edf, as.data.frame(sw))
> edf2
     characters birthdates       occupations gender
1  Walter White 1959-09-07           chemist   male
2 Jesse Pinkman 1984-09-14 meth manufacturer   male
3  Saul Goodman       <NA>   criminal lawyer   male
4  Skyler White 1970-08-11  car wash manager female

Evidemment, lorsqu’on ajoute des lignes ou des colonnes à une data.frame, la taille de ce qu’on rajoute doit correspondre à la taille de la data.frame originale:

> nrow(df) == nrow(as.data.frame(gender))
[1] TRUE
> ncol(edf) == ncol(as.data.frame(sw))
[1] TRUE

Avertissement

Cette façon d’ajouter des données à une data.frame est parfaitement valide mais fait appel à des fonctions R core. On utilisera dans le chapitre Transformation de données une méthode plus efficace avec la fonction mutate() du package dplyr de l’éco système Tidyverse.

11.2.3. Sélection de ligne et de colonne

Au delà d’une simple vue globale de la data.frame, on a régulièrement besoin de sélectionner des sous sections particulières pour conduire une analyse poussée sur certaines variables (colonnes) et/ou observations (lignes) qu’il faut sélectionner.

La sélection de colonnes est possible en utilisant l’index de la ou des colonnes concernées:

> iris[,1] # all the rows, column selection by index - output = double
[1] 5.1 4.9 4.7 4.6 5.0 5.4 ...

Puisqu’une data.frame est aussi une list, on peut également utiliser l’opérateur d’indexation de la list:

> iris[[1]] # implicit column selection by index - output = double
> [1] 5.1 4.9 4.7 4.6 5.0 5.4 ...
> class(iris[[1]])
[1] "numeric"
> typeof(iris[[1]])
[1] "double"

Lorsque qu’un seul indice est fourni, il est utilisé pour sélectionner la variable (colonne):

> iris[1] # implicit column selection by index - output = list
    Sepal.Length
1            5.1
2            4.9
3            4.7
4            4.6
5            5.0
6            5.4
...          ...
> class(iris[1])
[1] "data.frame"
> typeof(iris[1])
[1] "list"

Cette syntaxe est identique à iris[,1]:

> all(iris[1] == iris[,1])
[1] TRUE

On peut également récupérer le contenu de la colonne en indexant avec le nom:

> iris[,"Sepal.Length"]  # all the rows, column selection by name - output = double
[1] 5.1 4.9 4.7 4.6 5.0 5.4 ...

> iris["Sepal.Length"] # implicit column selection by name - output = list
    Sepal.Length
1            5.1
2            4.9
3            4.7
4            4.6
5            5.0
6            5.4
...          ...
> class(iris["Sepal.Length"])
[1] "data.frame"
> typeof(iris["Sepal.Length"])
[1] "list"

On peut également utiliser l’opérateur $ pour simplifier l’écriture:

> iris$Sepal.Length
[1] 5.1 4.9 4.7 4.6 5.0 5.4 ...

La sélection des observations (lignes) obéit au même principe, mais la sélection implicite n’est pas disponible puisqu’elle s’applique aux variables (colonnes):

> iris[1,] # row selection by index, all the columns - output = list
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
> class(iris[1,])
[1] "data.frame"
> typeof(iris[1,])
[1] "list"

Souvent les observations (lignes) ne sont pas nommées ou le sont avec l’index:

> rownames(iris)
  [1] "1"   "2"   "3"   "4"   "5"   "6"   "7"   "8"   "9"   "10"  "11"  "12"  "13"  "14"  "15"  "16"  "17"  "18"  "19"
 [20] "20"  "21"  "22"  "23"  "24"  "25"  "26"  "27"  "28"  "29"  "30"  "31"  "32"  "33"  "34"  "35"  "36"  "37"  "38"
 [39] "39"  "40"  "41"  "42"  "43"  "44"  "45"  "46"  "47"  "48"  "49"  "50"  "51"  "52"  "53"  "54"  "55"  "56"  "57"
 [58] "58"  "59"  "60"  "61"  "62"  "63"  "64"  "65"  "66"  "67"  "68"  "69"  "70"  "71"  "72"  "73"  "74"  "75"  "76"
 [77] "77"  "78"  "79"  "80"  "81"  "82"  "83"  "84"  "85"  "86"  "87"  "88"  "89"  "90"  "91"  "92"  "93"  "94"  "95"
 [96] "96"  "97"  "98"  "99"  "100" "101" "102" "103" "104" "105" "106" "107" "108" "109" "110" "111" "112" "113" "114"
[115] "115" "116" "117" "118" "119" "120" "121" "122" "123" "124" "125" "126" "127" "128" "129" "130" "131" "132" "133"
[134] "134" "135" "136" "137" "138" "139" "140" "141" "142" "143" "144" "145" "146" "147" "148" "149" "150"

Et dans ce cas, le nom et l’index étant identiques à un mode près (l’un est numérique, l’autre est une chaîne de caractères), il n’y a aucune différence entre les deux:

    > all(iris[1,] == iris["1",])
    [1] TRUE

    > identical(u1,u2)
[1] TRUE

Bien sûr, on peut mixer la sélection de variables et d’observations:

> iris[1,1]
[1] 5.1

> iris[1,"Sepal.Length"]
[1] 5.1

> iris$Sepal.Length[1]
[1] 5.1

Pour obtenir une plage de valeurs, on passe des vecteurs en lieu et place des simples indices:

> iris[c(1,6),c("Sepal.Length", "Sepal.Width")]
  Sepal.Length Sepal.Width
1          5.1         3.5
6          5.4         3.9

> iris[1:6,c("Sepal.Length", "Sepal.Width")]
  Sepal.Length Sepal.Width
1          5.1         3.5
2          4.9         3.0
3          4.7         3.2
4          4.6         3.1
5          5.0         3.6
6          5.4         3.9

11.2.4. Indexation logique

On a vu dans le chapitre Vecteurs que les vecteurs logiques sont construit par un prédicat:

> iris$Sepal.Length < 6 & iris$Sepal.Length > 5
[1]  TRUE FALSE FALSE FALSE FALSE  TRUE ...

On peut utiliser un vecteur logique pour sélectionner les observations satisfaisant à certaines conditions exprimées sur les variables:

> iris[iris$Sepal.Length >= 5.7 & iris$Sepal.Length <= 5.8 & iris$Sepal.Width >=3,]
   Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
15          5.8         4.0          1.2         0.2     setosa
16          5.7         4.4          1.5         0.4     setosa
19          5.7         3.8          1.7         0.3     setosa
96          5.7         3.0          4.2         1.2 versicolor

L’expression ci dessus contient une data.frame, sur laquelle on peut appliquer l’opérateur de sélection de variable:

> iris[iris$Sepal.Length >= 5.7 & iris$Sepal.Length <= 5.8 & iris$Sepal.Width >=3,]$Petal.Width
[1] 0.2 0.4 0.3 1.2

Pour des questions de lisibilité et de maintenance, c’est une bonne pratique d’écrire la condition de sélection à un seul endroit dans le code et d’y faire référence ensuite:

> fv <- iris$Sepal.Length >= 5.7 & iris$Sepal.Length <= 5.8 & iris$Sepal.Width >=3
> iris[fv,]$Petal.Width
[1] 0.2 0.4 0.3 1.2

11.2.5. Construire un sous ensemble de données

Pour être exhaustif, la fonction subset() (R core) peut être utilisée pour construire un sous ensemble des données de base:

> sdf <- subset(iris, subset=Petal.Length >=1.7, select = c(Sepal.Length, Sepal.Width))
> sdf
    Sepal.Length Sepal.Width
6            5.4         3.9
19           5.7         3.8
21           5.4         3.4
24           5.1         3.3
25           4.8         3.4
...
148          6.5         3.0
149          6.2         3.4
150          5.9         3.0

Le paramètre subset est utilisé pour filtrer les observations et le paramètre select pour filtrer les variables.

Avertissement

Les fonctions R core ne sont pas recommandées et on préférera les fonctions dplyr::filter() et dplyr::select() de l’éco système Tidyverse. Elles seront abordées dans le chapitre Transformation de données.

11.2.6. Rechercher les éléments dupliqués et les éléments uniques

Il peut y avoir des données dupliquées dans une data.frame. Selon le contexte de l’étude, on les conservera ou pas, mais il est intéressant de les identifier. La fonction duplicated() retourne un vecteur logique indiquant les éléments dupliqués:

> duplicated(iris)
  ...
[134] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE  TRUE FALSE ...

On se rappelle qu’on peut effectuer un comptage avec la fonction table():

> table(duplicated(iris))

FALSE  TRUE
  149     1

On affiche l’observation dupliquée en utilisant le vecteur logique retourné par duplicated() comme vecteur d’indexation:

> iris[duplicated(iris),]
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
143          5.8         2.7          5.1         1.9 virginica

Par défaut, duplicated() retourne l’indice de plus haut rang de l’observation dupliquée. On peut cependant lui passer l’argument logique fromLast pour inverser le sens de la recherche et retourner l’élément de plus faible indice:

> iris[duplicated(iris, fromLast = T),]
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
102          5.8         2.7          5.1         1.9 virginica

Récupérer tous les éléments dupliqués est possible en utilisant l’opérateur logique OR appliqué aux deux sens de recherche:

> iris[duplicated(iris) | duplicated(iris, fromLast = T),]
    Sepal.Length Sepal.Width Petal.Length Petal.Width   Species
102          5.8         2.7          5.1         1.9 virginica
143          5.8         2.7          5.1         1.9 virginica

Avertissement

La fonction R Core duplicated() sera avantageusement remplacé par la fonction dplyr::filter() disponible dans l’éco système Tidyverse. Il faudra utiliser la fonction de comptage (n()>1) comme argument.

Les éléments uniques sont récupérés grâce à la fonction R core unique() qui retourne un sous ensemble de la data.frame originale:

> dim(iris)
[1] 150   5
> u1 <- unique(iris)
> dim(u1)
[1] 149   5

Avertissement

La fonction R Core unique() sera avantageusement remplacé par la fonction dplyr::distinct() disponible dans l’éco système Tidyverse.

11.2.7. Le tri des données

Pour la présentation des données, il est parfois nécessaire de trier une variable de la data.frame selon un ou plusieurs critères. On peut utiliser la fonction R core order() qui retourne, non pas les valeurs elles mêmes, mais un vecteur d’une permutation des indices:

> vpi <- order(iris$Sepal.Length)
    > vpi
  [1]  14   9  39  43  42 ...

On utilise ce vecteur pour présenter les données triées:

> iris[vpi,]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
14           4.3         3.0          1.1         0.1     setosa
9            4.4         2.9          1.4         0.2     setosa
39           4.4         3.0          1.3         0.2     setosa
43           4.4         3.2          1.3         0.2     setosa
42           4.5         2.3          1.3         0.3     setosa
...

On observe ci dessus que plusieurs observations ont une variable Sepal.Length identique. On peut passer un deuxième argument à la fonction order(). Il sera utilisé comme critère secondaire pour le tri:

> iris[order(iris$Sepal.Length, iris$Petal.Length),]
    Sepal.Length Sepal.Width Petal.Length Petal.Width    Species
14           4.3         3.0          1.1         0.1     setosa
39           4.4         3.0          1.3         0.2     setosa
43           4.4         3.2          1.3         0.2     setosa
9            4.4         2.9          1.4         0.2     setosa
42           4.5         2.3          1.3         0.3     setosa
...

La data.frame est ici triée, d’abord par rapport à la variable Sepal.Length, et ensuite les valeurs identiques sont ordonnées avec la variable Petal.Length.

11.3. Des graphiques !

Les structures de données étudiées jusqu’à présent (vecteurs, arrays, matrices) peuvent évidemment être affichés dans des graphiques avec les anciens systèmes graphiques de R base et lattice, mais ils sont limités, complexes à utiliser et incompatibles avec le package graphique qui fait référence : ggplot2.

La structure de données de base pour ggplot2 est la data.frame, dont nous connaissons maintenant les caractéristiques élémentaires.

On a déjà eu un aperçu des possibilités étendues de ggplot2 dans le chapitre Un aperçu du fonctionnement de R. Mais ggplot2 peut lui même être étendu avec d’autres packages graphiques, lorsque l’on a à construire des graphiques complexes. On va s’intéresser ici au package GGally et à sa fonction ggpairs() qui permet de produire une série de scatter plots en une seule instruction:

> install.packages("GGally")
...
> library(GGally)
> p <- ggpairs(iris, mapping = aes(color = Species))
> p <- p + theme(axis.text.x=element_text(angle=90,hjust=1,vjust=0.5))
> p
_images/09-dataframes-fig03.png

Chaque graphique élémentaire peut évidemment être construit séparément.

11.3.1. Density plot

L’affichage de la densité utilise la fonction geom_density() geom. Le paramètre alpha permet de contrôler la transparence:

> ggplot(iris, aes(x=Sepal.Length, fill=Species)) + geom_density(alpha=0.4)
_images/09-dataframes-fig04.png

11.3.2. Histogrammes

On construit un histogramme avec la fonction geom_histogram() et le layer facetting:

> p <- ggplot(iris, aes(x=Sepal.Length, fill=Species))
> p <- p + geom_histogram()
> p <- p + facet_wrap(~Species, ncol=1)
> p
_images/09-dataframes-fig05.png

11.3.3. Scatter plots

On a déjà tracé des scatter (ou dot) plots dans le chapitre Un aperçu du fonctionnement de R:

> p <- ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width))
> p <- p + geom_point(aes(color=Species))
> p
_images/09-dataframes-fig06.png

11.3.4. Ajouter des labels

Le graphique complexe produit par la fonction GGally::ggpairs() comporte plusieurs types de sous graphiques. Un de ces types est un affichage textuel de données de corrélation.

La corrélation est une mesure de la « similitude » de deux variables (lire cet article pour se rafraichir la mémoire). On calcule une corrélation avec la fonction cor() appliquée à la data.frame entière ou à une sous partie:

> cor(subset(iris, select=-Species)) # toutes les observations, toutes les variables à l'exception de "Species"

             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length    1.0000000  -0.1175698    0.8717538   0.8179411
Sepal.Width    -0.1175698   1.0000000   -0.4284401  -0.3661259
Petal.Length    0.8717538  -0.4284401    1.0000000   0.9628654
Petal.Width     0.8179411  -0.3661259    0.9628654   1.0000000

> cor(subset(iris, Species=="setosa", select=-Species)) # les observations "setosa", toutes les variables à l'exception de "Species"

             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length    1.0000000   0.7425467    0.2671758   0.2780984
Sepal.Width     0.7425467   1.0000000    0.1777000   0.2327520
Petal.Length    0.2671758   0.1777000    1.0000000   0.3316300
Petal.Width     0.2780984   0.2327520    0.3316300   1.0000000

> cor(subset(iris, Species=="versicolor", select=-Species)) # les observations "versicolor", toutes les variables à l'exception de "Species"

             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length    1.0000000   0.5259107    0.7540490   0.5464611
Sepal.Width     0.5259107   1.0000000    0.5605221   0.6639987
Petal.Length    0.7540490   0.5605221    1.0000000   0.7866681
Petal.Width     0.5464611   0.6639987    0.7866681   1.0000000

> cor(subset(iris, Species=="virginica", select=-Species)) # les observations "virginica", toutes les variables à l'exception de "Species"

             Sepal.Length Sepal.Width Petal.Length Petal.Width
Sepal.Length    1.0000000   0.4572278    0.8642247   0.2811077
Sepal.Width     0.4572278   1.0000000    0.4010446   0.5377280
Petal.Length    0.8642247   0.4010446    1.0000000   0.3221082
Petal.Width     0.2811077   0.5377280    0.3221082   1.0000000

On pourrait aggréger les données de corrélation dans une data.frame avec les fonctions R core (rbind(), cbind() ou merge()) déjà rencontrées mais il est suffisant de connaitre l’existence de ces fonctions un peu obsolètes pour identifier leur rôle dans un code pratique. La bonne pratique sera d’utiliser les fonctions de l’éco système Tidyverse :

  • tibble::rownames_to_column()

  • dplyr::bind_rows()

  • dplyr::mutate()

  • dplyr::relocate().

Le code Tidyverse pour générer la data.frame est donné ici à titre indicatif (on travaillera spécifiquement sur les fonctions Tidyverse dans le chapitre suivant):

> library(tibble) # un package tidyverse nécessaire
> library(dplyr) # un autre package tidyverse nécessaire
# Construction des data frames de corrélation
> a <- as.data.frame(cor(subset(iris, select=-Species)))
> b <- as.data.frame(cor(subset(iris,Species=="setosa", select=-Species)))
> c <- as.data.frame(cor(subset(iris,Species=="versicolor", select=-Species)))
> d <- as.data.frame(cor(subset(iris,Species=="virginica", select=-Species)))
# Ajout d'une colonne
> a <- rownames_to_column(a,var="SP.WL")
> b <- rownames_to_column(b,var="SP.WL")
> c <- rownames_to_column(c,var="SP.WL")
> d <- rownames_to_column(d,var="SP.WL")
# On "stacke" les data frames élémentaires
> cordf <- bind_rows(a, b, c, d)
# Création du vecteur species
> species <- rep(c("all", "setosa", "versicolor", "virginica"), each=4)
# Ajout d'une variable Species
> cordf <- mutate(cordf, Species=species)
# Déplacement de la variable en première position
> cordf <- relocate(cordf, Species)

La data.frame obtenue:

> cordf

      Species        SP.WL Sepal.Length Sepal.Width Petal.Length Petal.Width
1         all Sepal.Length    1.0000000  -0.1175698    0.8717538   0.8179411
2         all  Sepal.Width   -0.1175698   1.0000000   -0.4284401  -0.3661259
3         all Petal.Length    0.8717538  -0.4284401    1.0000000   0.9628654
4         all  Petal.Width    0.8179411  -0.3661259    0.9628654   1.0000000
5      setosa Sepal.Length    1.0000000   0.7425467    0.2671758   0.2780984
6      setosa  Sepal.Width    0.7425467   1.0000000    0.1777000   0.2327520
7      setosa Petal.Length    0.2671758   0.1777000    1.0000000   0.3316300
8      setosa  Petal.Width    0.2780984   0.2327520    0.3316300   1.0000000
9  versicolor Sepal.Length    1.0000000   0.5259107    0.7540490   0.5464611
10 versicolor  Sepal.Width    0.5259107   1.0000000    0.5605221   0.6639987
11 versicolor Petal.Length    0.7540490   0.5605221    1.0000000   0.7866681
12 versicolor  Petal.Width    0.5464611   0.6639987    0.7866681   1.0000000
13  virginica Sepal.Length    1.0000000   0.4572278    0.8642247   0.2811077
14  virginica  Sepal.Width    0.4572278   1.0000000    0.4010446   0.5377280
15  virginica Petal.Length    0.8642247   0.4010446    1.0000000   0.3221082
16  virginica  Petal.Width    0.2811077   0.5377280    0.3221082   1.0000000

On peut maintenant extraire les informations de cordf pour les afficher en mode texte:

> p <- ggplot(data=subset(cordf,SP.WL=="Sepal.Width"), aes(x=4,y=c(4:1)))
> p <- p + geom_text(aes(label= sprintf("%18s: %1.3f",Species, Sepal.Length), color=Species), size=8)
> p <- p + theme(legend.position="none")
> p
_images/09-dataframes-fig07.png

Quelques réglages supplémentaires seraient nécessaires pour formatter le texte, modifier les couleurs, supprimer les labels et la légende, etc.

11.3.5. Boxplots

Un boxplot est une façon très compacte de présenter quelques statistiques descriptives (médiane, 1er et 3eme quantiles, valeurs min et max).

> p <- ggplot(iris, aes(x=Species, y=Sepal.Length, fill=Species))
> p <- p + geom_boxplot()
> p
_images/09-dataframes-fig08.png

Dans le même esprit, le violin plot fait apparaitre une information sur la forme de la distribution:

> p <- ggplot(iris, aes(x=Species, y=Sepal.Length, fill=Species))
> p <- p + geom_violin()
> p
_images/09-dataframes-fig09.png

11.4. Application : la qualité de l’air en Ile de France

L’association AirParif gère des dizaines de capteurs de qualité de l’air en Ile de France. Nous allons nous intéresser ici à celui de Lognes (le plus proche géographiquement d’ESIEE Paris. Les données que l’on va utiliser ont été collectées en avril 2015. Le dataset contient 720 obervations de 5 variables:

  • Date

  • Hour

  • PM10 (µg/m3)

  • NO2 (µg/m3)

  • O3 (µg/m3)

Pour pouvoir exploiter pleinement les résultats de l’analyse, on s’intéressera à l’explication fournie ici.

11.4.1. Setup

  1. Démarrer R Studio et fermer tous les projets ouverts avec R Studio > File > Close all

  2. Créer un nouveau projet AirQuality avec R Studio > File > New project... > New Directory > New Project

La data.frame contenant les données est stockée dans l’objet air.RData.

On la charge dans l’environnement avec la commande:

> load("air.RData")

Utiliser les fonctions d’exploration textuelle des données (head(), summary(), str(), etc…) pour une première approche. Identifier les variables présentes, leur nature, les valeurs qu’elles prennent. En un mot, comprendre la totalité des informations présentées:

> summary(air)
      date                heure     PM10.microg.m3. NO2.microg.m3.  O3.microg.m3.
 Min.   :2015-04-01   1      : 30   Min.   : 2.00   Min.   :  5.0   Min.   :  0.00
 1st Qu.:2015-04-08   2      : 30   1st Qu.:14.00   1st Qu.: 14.0   1st Qu.: 26.00
 Median :2015-04-15   3      : 30   Median :19.00   Median : 22.0   Median : 54.00
 Mean   :2015-04-15   4      : 30   Mean   :21.63   Mean   : 30.6   Mean   : 51.54
 3rd Qu.:2015-04-23   5      : 30   3rd Qu.:27.00   3rd Qu.: 40.5   3rd Qu.: 76.00
 Max.   :2015-04-30   6      : 30   Max.   :65.00   Max.   :127.0   Max.   :116.00
                      (Other):540   NA's   :10      NA's   :1       NA's   :3

Utiliser la fonction View() pour une présentation structurée de l’ensemble des données.

La fonction sapply() permet d’appliquer une autre fonction à l’ensemble des variables. Le premier paramètre est la data.frame, le second la fonction à appliquer. Ici la fonction class() retourne le type de chaque colonne:

> sapply(air, class)
           date           heure PM10.microg.m3.  NO2.microg.m3.   O3.microg.m3.
         "Date"        "factor"       "numeric"       "numeric"       "numeric"

Avertissement

La fonction sapply() est une fonction R core. L’éco système Tidyverse fournira des solutions plus efficaces.

La fonction typeof() en donne une représentation interne:

> sapply(air, typeof)
           date           heure PM10.microg.m3.  NO2.microg.m3.   O3.microg.m3.
       "double"       "integer"        "double"        "double"        "double"

On peut observer que R stocke les dates et les heures sous la forme de doubles.

Important

L’analyse des données est un processus impliquant l’inspection, le nettoyage, la transformation et la modélisation de ces données dans le but de découvrir des informations utiles, de suggérer quelques conclusions et d’aider à la prise de décision [Wikipedia]

11.4.2. Inspection

Visualiser les données est généralement un bon point de départ. Un simple plot peut révéler la façon dont les données sont distribuées et donner une bonne idée des premiers éléments statistiques.

Tracer l’évolution des particules PM10 en fonction de la date. Le graphique obtenu devrait ressembler à ceci.

_images/09-dataframes-fig10.png

Ajouter une fonction geom_smooth() pour avoir une idée générale de la variation des données.

_images/09-dataframes-fig11.png

Il serait intéressant de tracer une série de boxplot comme on l’a fait avec le dataset iris. Cependant la constitution du dataset rend les choses difficiles puisque certaines informations sont stockées dans le nom de la variable. Par exemple la nature du polluant:

> head(air)
                date heure PM10.microg.m3. NO2.microg.m3. O3.microg.m3.
1 2015-04-01     1              21              8            79
2 2015-04-01     2              18              9            78
3 2015-04-01     3              16             10            75
4 2015-04-01     4              14             12            69
5 2015-04-01     5               8             21            60
6 2015-04-01     6              10             33            49

A comparer avec le dataset iris pour lequel la variété de fleur est stockée dans une variable:

> head(iris)
  Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1          5.1         3.5          1.4         0.2  setosa
2          4.9         3.0          1.4         0.2  setosa
3          4.7         3.2          1.3         0.2  setosa
4          4.6         3.1          1.5         0.2  setosa
5          5.0         3.6          1.4         0.2  setosa
6          5.4         3.9          1.7         0.4  setosa

Imaginer la forme que devraient prendre les données de pollution pour se conformer à la structure du dataset iris. En écrire les 5 premières lignes.

11.5. Ressources additionnelles

La documentation de ggplot2 est indispensable pour maitriser la création de graphiques.