.. toctree:: :maxdepth: 2 .. include:: weblinks.txt .. _dataframes: 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 :ref:`overview`, 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. Construire une data frame ------------------------- Une ``data.frame`` peut être construite à partir de vecteurs (satisfaisant les conditions ci dessus) avec la fonction :func:`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 criminal lawyer .. tip:: Une liste dont les éléments satisfont les conventions ci dessus peut être transformée en ``data.frame`` avec la fonction :func:`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 :func:`read.table`. On abordera cette façon de faire dans le chapitre :ref:`files`. 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" .. image:: images/09-dataframes-fig01.jpg :scale: 100 % 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. Caractéristiques ................ Les fonctions :func:`dim`, :func:`colnames` et :func:`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 :func:`head` et :func:`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 :func:`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 :func:`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 :func:`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 :func:`View` qui met en avance la similitude avec une feuille de tableur:: > View(iris) .. image:: images/09-dataframes-fig02.png :scale: 50 % :align: center 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 :func:`cbind` et :func:`rbind`. C'est une bonne pratique de pratiquer une coercition explicite avec la fonction :func:`as.data.frame`: .. code-block:: r > 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 criminal lawyer male L'utilisation de la fonction :func:`rbind` nécessite de formatter les lignes à ajouter dans une ``data.frame`` avec des noms de variables (colonnes) identiques: .. code-block:: r > 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 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 .. warning:: 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 :ref:`transforming` une méthode plus efficace avec la fonction :func:`mutate` du package ``dplyr`` de l'éco système **Tidyverse**. 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 Indexation logique .................. On a vu dans le chapitre :ref:`vectors` que les vecteurs logiques sont construit par un prédicat: .. code-block:: r > 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: .. code-block:: r > 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: .. code-block:: r > 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: .. code-block:: r > 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 Construire un sous ensemble de données ...................................... Pour être exhaustif, la fonction :func:`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. .. warning:: Les fonctions R core ne sont pas recommandées et on préférera les fonctions :func:`dplyr::filter` et :func:`dplyr::select` de l'éco système **Tidyverse**. Elles seront abordées dans le chapitre :ref:`transforming`. 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 :func:`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 :func:`table`:: > table(duplicated(iris)) FALSE TRUE 149 1 On affiche l'observation dupliquée en utilisant le vecteur logique retourné par :func:`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, :func:`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 .. warning:: La fonction R Core :func:`duplicated` sera avantageusement remplacé par la fonction :func:`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 :func:`unique` qui retourne un sous ensemble de la ``data.frame`` originale:: > dim(iris) [1] 150 5 > u1 <- unique(iris) > dim(u1) [1] 149 5 .. warning:: La fonction R Core :func:`unique` sera avantageusement remplacé par la fonction :func:`dplyr::distinct` disponible dans l'éco système **Tidyverse**. 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 :func:`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 :func:`order`. Il sera utilisé comme critère secondaire pour le tri: .. code-block:: r > 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``. 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 :ref:`overview`. 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 :func:`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 .. image:: images/09-dataframes-fig03.png :scale: 80 % :align: center Chaque graphique élémentaire peut évidemment être construit séparément. Density plot ............ L'affichage de la densité utilise la fonction :func:`geom_density` geom. Le paramètre ``alpha`` permet de contrôler la transparence: .. code-block:: r > ggplot(iris, aes(x=Sepal.Length, fill=Species)) + geom_density(alpha=0.4) .. image:: images/09-dataframes-fig04.png :scale: 80 % :align: center Histogrammes ............ On construit un histogramme avec la fonction :func:`geom_histogram` et le layer ``facetting``: .. code-block:: r > p <- ggplot(iris, aes(x=Sepal.Length, fill=Species)) > p <- p + geom_histogram() > p <- p + facet_wrap(~Species, ncol=1) > p .. image:: images/09-dataframes-fig05.png :scale: 80 % :align: center Scatter plots ............. On a déjà tracé des scatter (ou dot) plots dans le chapitre :ref:`overview`: .. code-block:: r > p <- ggplot(iris, aes(x=Sepal.Length, y=Sepal.Width)) > p <- p + geom_point(aes(color=Species)) > p .. image:: images/09-dataframes-fig06.png :scale: 80 % :align: center Ajouter des labels .................. Le graphique complexe produit par la fonction :func:`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 :func:`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 (:func:`rbind`, :func:`cbind` ou :func:`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** : - :func:`tibble::rownames_to_column` - :func:`dplyr::bind_rows` - :func:`dplyr::mutate` - :func:`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 .. image:: images/09-dataframes-fig07.png :scale: 80 % :align: center Quelques réglages supplémentaires seraient nécessaires pour formatter le texte, modifier les couleurs, supprimer les labels et la légende, etc. 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). .. code-block:: r > p <- ggplot(iris, aes(x=Species, y=Sepal.Length, fill=Species)) > p <- p + geom_boxplot() > p .. image:: images/09-dataframes-fig08.png :scale: 80 % :align: center Dans le même esprit, le `violin plot `_ fait apparaitre une information sur la forme de la distribution: .. code-block:: r > p <- ggplot(iris, aes(x=Species, y=Sepal.Length, fill=Species)) > p <- p + geom_violin() > p .. image:: images/09-dataframes-fig09.png :scale: 80 % :align: center 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 `_. Setup ..... #. Démarrer R Studio et fermer tous les projets ouverts avec :command:`R Studio > File > Close all` #. Créer un nouveau projet **AirQuality** avec :command:`R Studio > File > New project... > New Directory > New Project` La ``data.frame`` contenant les données est stockée dans l'objet :download:`air.RData `. On la charge dans l'environnement avec la commande:: > load("air.RData") Utiliser les fonctions d'exploration textuelle des données (:func:`head`, :func:`summary`, :func:`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 .. There are 30 values for each hour, corresponding to 30 days in April. Utiliser la fonction :func:`View` pour une présentation structurée de l'ensemble des données. La fonction :func:`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 :func:`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" .. warning:: La fonction :func:`sapply` est une fonction R core. L'éco système **Tidyverse** fournira des solutions plus efficaces. La fonction :func:`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] 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. .. image:: images/09-dataframes-fig10.png :scale: 80 % :align: center .. > p <- ggplot(air, aes(x=date, y=PM10.microg.m3.)) > p <- p + geom_point() > p Ajouter une fonction :func:`geom_smooth` pour avoir une idée générale de la variation des données. .. image:: images/09-dataframes-fig11.png :scale: 80 % :align: center .. > p <- ggplot(air, aes(x=date, y=PM10.microg.m3.)) > p <- p + geom_point() > p <- p + geom_smooth() > p 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. Ressources additionnelles ------------------------- La documentation de `ggplot2 `_ est indispensable pour maitriser la création de graphiques.