Les fonctionnalités de base du DataFrame

Une fois nos données importées, on peut commencer à faire nos analyses. Pour manipuler les données en R, nous recommandons l'utilisation du data.frame. Le data.frame est une structure de données propre à R qui se présente sous forme de tableau. Le data.frame nous donne accès à de multiples fonctionnalités nous permettant de manipuler les données. Dans cette section nous verrons quelques-unes de ces fonctionnalités
. Dans cette section, nous allons apprendre :

  1. Comment utiliser les fonction head() et tail() pour examiner une partie des données du data.frame
  2. Comment utiliser la fonction str() pour avoir une série d'informations à propos de notre data.frame
  3. Comment créer un data.frame.
  4. Comment accéder aux éléments du data.frame

Dans cette section, nous allons utiliser des données tirées de l'enquête Histoire de vie 2003, que nous avons importées préalablement et assignées à la variable hdv2003.

Les fonctions head() et tail()

La première fonction à laquelle nous allons nous intéresser est la fonction head(). Cette fonction permet d'imprimer les quelques premières lignes du data.frame, (6 par défaut). Cela permet d'avoir une idée de la structure des données. Faisons donc cela avec les données hdv2003.

head(hdv2003)


On peut spécifier le nombre de lignes qu'on veut voir grâce au paramètre n. Ici, on imprime donc les 5 premières lignes seulement.

head(hdv2003, n=5)


La fonction tail() fonctionne de la même manière que head à la différence qu'elle imprime les dernières lignes du data.frame.

tail(hdv2003, n=5)


La structure du data.frame

Une autre fonction très utile est la fonction str() (str est l'abréviation de structure). Cette fonction permet d'avoir un ensemble d'informations à propos du data.frame avec lequel on travaille. À première vue, ce que nous retourne la fonction str() peut sembler indigeste, mais lorsqu'on sait comment lire ce qui est retourné, ce n'est pas très compliqué.

str(hdv2003)
## 'data.frame':    2000 obs. of  6 variables:
##  $ id           : int  1 2 3 4 5 6 7 8 9 10 ...
##  $ age          : int  28 23 59 34 71 35 60 47 20 28 ...
##  $ genre        : Factor w/ 2 levels "Homme","Femme": 2 2 1 1 2 2 2 1 2 1 ...
##  $ poids        : num  2634 9738 3994 5732 4329 ...
##  $ freres.soeurs: int  8 2 2 1 0 5 1 5 4 2 ...
##  $ heures.tv    : num  0 1 0 2 3 2 2.9 1 2 2 ...


À la première ligne, on apprend d'abord que hdv2003 est un data.frame, ce qu'on savait déjà. On voit aussi qu'il y a 2000 obs. soit 2000 lignes (ou 2000 observations), puis on apprend qu'il y a 5 variables ou 5 colonnes. Chaque ligne qui suit nous informe sur le type de variable que contient chacune des colonnes du data.frame. On peut donc voir d'abord le nom de chaque colonne, suivi du type de variable qu'elle contient, puis d'exemple de valeurs contenues dans cette colonne (les quelques premières observations). Si le type de variable est un Factor, on indique les valeurs possibles que peut prendre chaque observation, puis on substitue ces valeurs par un nombre qui représente chacune des valeurs possible (par exemple Femme : 1, Homme : 2). Une chose qu'on peut déjà comprendre à propos des data.frame, est que chaque colonne doit contenir des variables du même type.

Créer un data.frame à partir de vecteurs

Voyons maintenant comment on peut créer un data.frame à partir de vecteurs. On crée ici un data.frame similaire à celui qu'on a vu dans la section précédente. Pour créer un data.frame à partir de vecteurs, il y a quelques règles simples qu'on doit respecter. 1. Chaque vecteur doit être de la même taille, sinon la fonction data.frame() nous enverra un message d'erreur. 2. Chaque élément de chaque vecteur doit être du même type. Si ce n'est pas le cas, R risque de faire une conversion automatiquement, ce qui pourrait entraîner des comportements inattendus. Il suffit d'appeler la fonction data.frame() et de lui passer tous les vecteurs qu'on veut retrouver dans notre data.frame. Chaque colonne aura le nom de la variable contenant le vecteur.

id <- c(1,2,3,4,5)
age <- c(31,22,46,75,56)
ville <- c("Montréal", "Québec", "New York", "Rimouski", "Saint-Élie-de-Caxton")

df <- data.frame(id, age, ville)
df


Exercice

Créez un data.frame avec 2 colonnes. La première colonne mois contient les mois de l'année. La seconde colonne nombre_de_jours correspond au nombre de jours de chaque mois. Assignez le tout à df et imprimez le contenu.

mois <- c("Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre")
nombre_de_jours <- c(31, 28, 31, 30, 31, 30, 31, 31, 30,31, 30, 31)
df <- data.frame(mois, nombre_de_jours)
df

Accéder aux éléments du data.frame

Une des choses les plus utiles que nous permet de faire les data.frame est d'accéder à ses éléments ou à des tranches de celui-ci de manière extrêmement efficace. Il existe 4 manières d'aller chercher des tranches du data.frame. Deux s'opèrent sur les lignes, et deux s'opèrent sur les colonnes :
1. L'index des lignes.
2. Condition sur les lignes.
3. Index des colonnes.
4. Nom des colonnes.

Commençons par les points 1 et 3, l'indexation sur les lignes et les colonnes. data.frame utilise une méthode d'accès aux éléments qui est très intuitif. Cette méthode utilise les [crochets]. La structure d'accès est la suivante :
[ index_ligne_départ : index_ligne_fin , index_colonne_départ : index_colonne_fin ]
Par exemple, df[1:3,4:5] nous donnerait accès à la ligne 1 à 3 et la colonne 4 et 5 du data.frame df. Voyons quelques exemples avec les deux data.frame qu'on a utilisé jusqu'ici : df et hdv2003.

Lorsqu'on met un unique chiffre entre les crochets, on a accès à la colonne de l'index correspondant

df[,3]
## [1] Montréal             Québec               New York            
## [4] Rimouski             Saint-Élie-de-Caxton
## Levels: Montréal New York Québec Rimouski Saint-Élie-de-Caxton


Lorsqu'on écrit un chiffre avant la virgule uniquement, on accède à la ligne de l'index correspondant :

hdv2003[1,]


Lorsqu'il n'y a qu'un seul chiffre de chaque côté de la virgule, on accède à un élément particulière à l'intersection du ce numéro de ligne et de colonne :

hdv2003[1,1]
## [1] 1


Comme on l'a vu plus haut, on peut aussi accéder à des tranches de la manière suivante :

hdv2003[1:2,1:2]


Il est finalement possible d'indiquer explicitement les numéros de lignes et de colonnes qu'on souhaite sélectionner en mettant les index dans des vecteurs :

hdv2003[c(1,3,5),c(1,3,5)]


On peut aussi spécifier le nom des colonnes qu'on veut regarder. Si l'on veut sélectionner plus d'une colonne, on doit mettre le nom des colonnes dans un vecteur de la manière suivante :

hdv2003[1:3,c("id","genre")]

On peut aussi finalement sélectionner une colonne seule, qui nous est retournée sous forme de vecteur. Pour ce faire, il y a deux manières. Ou bien avec les crochets, ou bien avec l'opérateur $.

df[,"age"]
## [1] 31 22 46 75 56
df$age
## [1] 31 22 46 75 56


Une des fonctionnalités les plus puissantes du data.frame est l'accès par condition. Admettons qu'on veuille avoir accès aux données des femmes, on peut s'y prendre de la manière suivante. La condition hdv2003$genre == "Femme" signifie qu'on voudra toutes les lignes de hdv2003 ou le genre est "Femme". On passe ensuite nos conditions dans les crochets avant la virgule.

hdv2003[hdv2003$genre == "Femme",]


On peut aussi combiner des conditions avec des opérateurs logiques, comme on peut le voir aussi où l'on filtre les lignes pour n'avoir que les femmes de plus de 40 ans. Lorsqu'on a plus d'une condition, on doit les mettre entre parenthèses.

hdv2003[(hdv2003$genre == "Femme" & hdv2003$age > 40),]

Exercice

Dans hdv2003, imprimez les lignes des hommes qui écoutent la télé plus de 5 heures par semaine (cette information est donnée par la colonne heures.tv).

data(hdv2003)
hdv2003[(hdv2003$genre == "Homme" & hdv2003$heures.tv > 5),]

L'utilisation de la librairie dplyr

Les fonctionnalités du data.frame sont extrêmement utiles lorsqu'on veut faire des opérations simples. Cependant, lorsqu'on veut faire des opérations plus complexes, notre code peut rapidement devenir illisible. Voyons ici un exemple où l'on veut voir les données sur le nombre de freres et soeur des Femme de moins de 40 ans en l'ordonnant en ordre croissant d'âge. Avec les fonctionnalités du data.frame, cela donnerait quelque chose comme ça :

data(hdv2003)
hdv2003[(hdv2003$age < 40 & hdv2003$genre == "Femme"), c("id","age","freres.soeurs")][order(hdv2003[(hdv2003$age < 40 & hdv2003$genre == "Femme"),]$age),]


Installer une librairie

Une librairie est un ensemble de fonctions qui ont été programmées par d'autres personnes et qu'on peut utiliser. R vient avec un ensemble de fonctions. data.frame peut être considéré comme une librairie qui nous permet de manipuler des données. Une librairie très utile pour manipuler les données est dplyr. Comme cette librairie n'est pas native à R, on doit l'installer sur notre ordinateur. Pour ce faire, il suffit d'utiliser la fonction install.packages(). Pour installer dplyr, il suffit de l'utiliser de la manière suivante :

install.packages("dplyr")

La librairie dplyr

Lorsqu'on programme, on veut généralement que notre code reste lisible pour les autres et pour nous-mêmes (on sera peut-être amené à replonger dans notre code dans quelques mois). C'est pour cette raison qu'il est recommandé d'utiliser la librairie dplyr lorsque les opérations deviennent plus complexes. dplyr est une librairie qui offre un ensemble d'outils permettant d'écrire du code plus propre en R.
Les deux premières fonctions offertes par dplyr sont select() et filter().
select() permet de sélectionner des colonnes par nom, et filter() permet de filtrer par conditions. Admettons qu'on veut uniquement regarder les colonnes id age et freres.soeurs de la table hdv2003, on peut utiliser select(). Le premier paramètre contient le data.frame duquel on veut extraire des colonnes, puis les colonnes qu'on veut sélectionner. Nul besoin ici de les mettre entre guillemets, ou dans un vecteur.(Cette requête est équivalente à hdv2003[,c("id", "age", "freres.soeurs")])

select(hdv2003, id, age, freres.soeurs)


Si l'on veut filtrer notre data.frame par condition, on peut utiliser filter(). Admettons qu'on veuille voir les données sur les Hommes de moins de 30 ans de la table hdv2003, il suffit de passer notre table comme premier paramètre, suivi des différentes conditions, séparés par des virgules. (Cette requête est équivalente à hdv2003[(genre == "Homme" & age < 30),] )

filter(hdv2003, genre == "Homme", age < 30)


Jusqu'ici, il peut être difficile de voir l'avantage d'utiliser les fonctions de dplyr plutôt que ce qu'on a vu précédemment. En effet, en quoi select(hdv2003, id, age, freres.soeurs) est-il préférable à hdv2003[,c("id", "age", "freres.soeurs")] ? Ces deux commandes, après tout, ont à peu près la même taille et donnent le même résultat.
L'avantage de dplyr tient sur l'introduction d'un nouvel opérateur, le pipe, qui s'écrit %>%. Le pipe, nous permet d'enchaîner différentes fonctions les unes après les autres. Ce que le pipe fait, c'est qu'il prend ce qui vient avant lui, et l'envoie comme premier paramètre de la fonction qui vient après lui. Par exemple on pourrait réécrire la commande de sélection qu'on a vue plus haut avec un pipe. Ce que le pipe fait, dans le cas ci-bas, c'est qu'il prend hdv2003, et il l'envoie dans select() comme premier paramètre.

hdv2003 %>% 
  select(id, age, freres.soeurs)


Ce qui est avantageux avec le pipe, c'est qu'on peut enchaîner plusieurs commandes. On pourrait donc combiner filter() et select(). C'est ce que la commande suivante fait. Elle prend hdv2003, elle le passe dans filter() qui filtre les données pour ne conserver que les lignes des Hommes de moins de 30 ans, puis, elle passe les données filtrées à select() qui sélectionne les colonnes qui nous intéresse.

hdv2003 %>% 
  filter(genre == "Homme", age < 30) %>% 
  select(id, age, freres.soeurs) 


Pour accéder à des lignes par index, il suffit d'utiliser la fonction slice(). Ainsi, si l'on ajoute slice(1:5) à la fin de la chaîne précédente, on pourra voir uniquement les 5 premières lignes.

data(hdv2003)
hdv2003 %>% 
  filter(genre == "Homme", age < 30) %>% 
  select(id, age, freres.soeurs) %>% 
  slice(1:5)


Exercice

Imprimez les 10 premières lignes des hommes qui écoutent la télé plus de 5 heures par semaine, en prenant soin d'imprimer uniquement leur âge et leur occupation (colonne occup).

data(hdv2003)
hdv2003 %>% 
  filter(genre=="Homme", heures.tv>5) %>% 
  select(age, occup) %>% 
  slice(1:10)

Faire des calcules sur les colonnes

Qu'arrive-t-il si nous voulons dériver de nouvelles informations des données que nous avons? Pour cela, il faut utiliser mutate(). mutate() nous permet de faire des opérations sur les colonnes. Dans l'exemple qui suit, on calcule l'écart par rapport à la moyenne de poids de chaque personne. Pour ce faire, nous devons d'abord calculer la moyenne des poids (ce que vous verrez en plus de détails dans le tutoriel sur les statistiques descriptives). Ensuite, il suffit de nommer la nouvelle colonne qu'on veut voir apparaître, ici ecart_poids_moyenne, et indiquer comment on veut qu'elle soit calculée. R comprend ici qu'on doit soustraire la valeur de moyenne_poids à chaque poids individuel.

data(hdv2003)
moyenne_poids <- hdv2003$poids %>% mean()
hdv2003 %>% 
  mutate(ecart_poids_moyenne = poids - moyenne_poids) %>% 
  slice(1:5) %>% 
  select(id, age, genre, ecart_poids_moyenne)

Remarquer qu'il serait aussi possible de faire une opération entre deux colonnes (ou plus) ou sur une colonne toute seule. On peut même écraser les valeurs d'une colonne en utilisant un nom de colonne déjà existante dans mutate(). Vous pouvez lire les commentaires dans le code qui suit qui indique ce qui est fait.

data(hdv2003)
# Ici on arrondit la valeur de poids pour se débarrasser des nombres après la virgule 
# (c'est ce que fait la fonction round())
hdv2003 %>% 
  mutate(poids = round(poids))
#Une des variables de hdv2003 est le nombre d'heures de télé par jour que la personne regarde. En sachant son âge, on peut donc calculer (estimer) le nombre d'heures de télé qu'il ou elle a regardé dans toute sa vie.
hdv2003 %>% 
  mutate(nombre_heure_tv_a_vie = heures.tv * 365 * age) %>% head()


Chose importante à noter : mutate() ne change pas la valeur du data.frame directement. Si l'on désire que le changement s'opère directement sur le data.frame avec lequel on travaille, on doit lui réassigner le résultat, comme dans l'exemple qui suit :

hdv2003 <- hdv2003 %>% 
            mutate(poids = round(poids))


La dernière fonction de la librarie dplyr qu'on verra est arrange(). Cette fonction nous permet d'ordonner les données d'une colonne en ordre croissant ou décroissant. Par exemple, si l'on désire voir les données de hdv2003 ordonnés selon l'âge, il suffit de faire ceci :

hdv2003 %>% 
  arrange(age)


Pour ordonner par ordre décroissant, il faut ajouter la fonction desc() à l'intérieur de arrange comme ceci :

hdv2003 %>% 
  arrange(desc(age))


Revenons donc à la tâche du début de la section. Rappelons-nous qu'on voulait voir les données sur le nombre de freres et soeur des Femme de moins de 40 ans ordonnées en ordre croissant selon l'âge. Avec les fonctions de dplyr, on s'y prendrait de la manière suivante :

hdv2003 %>% 
  filter(genre == "Femme", age < 40) %>% 
  select(id, age, freres.soeurs) %>% 
  arrange(age)


C'est à notre avis beaucoup plus clair que ce qu'on avait en début de section.

Exercice

Sachant que les données de hdv2003 datent de 2003, ajustez la colonne age du data.frame afin qu'elle soit à jour en date d'aujourd'hui (par exemple, la présente année est 2022, on doit ajouter 19 ans à la colonne age).

data(hdv2003)
hdv2003 <- hdv2003 %>% 
  mutate(age = age + 19)

Gérer les données manquantes

Lorsqu'on travaille avec des données, il n'est pas rare de se rendre compte qu'il y a des données manquantes. Il existe plusieurs manières de gérer ce problème, mais aucune solution n'est idéale.
Dans cette section, nous allons apprendre :

  1. Comment trouver les données manquantes
  2. Comment supprimer les données manquantes
  3. Comment remplacer les données manquantes par d'autres valeurs

Trouver les données manquantes

En R, et dans plusieurs autres langages, les données manquantes sont généralement représentées par une expression spéciale, le NA (signifiant not available) ou le NaN (signifiant not a number). R nous fournit une fonction qui sert uniquement à trouver les NA, la fonction is.na().


Nous avons ajouté la colonne salaire à notre data.frame df, comme on peut le voir ici.

df


On remarque qu'il y a deux NA dans la colonne salaire. Voyons voir ce qui se passe lorsqu'on passe df dans is.na().

df %>% is.na()
##         id   age ville salaire
## [1,] FALSE FALSE FALSE   FALSE
## [2,] FALSE FALSE FALSE    TRUE
## [3,] FALSE FALSE FALSE    TRUE
## [4,] FALSE FALSE FALSE   FALSE
## [5,] FALSE FALSE FALSE   FALSE


On remarque que maintenant, chaque cellule contient ou bien la valeur TRUE, si cette cellule contenait un NA, ou FALSE dans le cas contraire. Avec la table df qui contient très peu de données, on pourrait très bien se limiter à cette utilisation de la fonction is.na() pour savoir si notre table contient des NA. Admettons, cependant, qu'on avait un data.frame qui contient des dizaines de milliers de lignes, et des dizaines de colonnes. Utiliser la fonction is.na() de cette manière imprimerait des dizaines de milliers de lignes de TRUE/FALSE. Il serait difficile de naviguer dans ce résultat. On ne veut pas ça.
Ce qu'on veut, dans un premier temps, c'est de savoir si notre table contient des données manquantes ou non. Pour savoir si au moins une donnée est manquante, on peut utiliser la fonction any(). any() prend en entré un vecteur, ou un data.frame, contenant uniquement les valeurs TRUE ou FALSE et retourne TRUE si au moins une valeur est TRUE, et retourne FALSE sinon.
Dans la ligne de code suivante, on passe le résultat de ce qu'on a fait précédemment à la fonction any() grâce au pipe.

df %>% is.na() %>% any()
## [1] TRUE


Le résultat, TRUE nous indique, sans surprise, que notre table contient au moins un NA.

On pourrait aussi regarder colonne par colonne, pour voir si l'une d'elles contient des NA. Par exemple ici, on regarde si la colonne id (à laquelle on accède grâce à l'opérateur $) contient des NA.

df$id %>% is.na() %>% any()
## [1] FALSE


La valeur de retour, FALSE, nous indique qu'il n'y a pas de NA dans la colonne id. Voyons voir pour la colonne salaire.

df$salaire %>% is.na() %>% any()
## [1] TRUE

La valeur de retour, TRUE, nous indique que la colonne salaire contient effectivement des NA, ce qu'on savait déjà.


Maintenant qu'on sait qu'une colonne a des valeurs manquantes, on peut vouloir regarder les lignes où se trouvent ces valeurs manquantes. Pour ce faire, on peut utiliser la fonction filter() qu'on a vue précédemment. La commande suivante peut être lue de la manière suivante. On demande à filter de renvoyer les lignes de df ou la fonction is.na() retourne TRUE avec la colonne salaire.

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


Exercice

Répondez à la question suivante : y a-t-il des données manquantes dans la colonne qualif de hdv2003

data(hdv2003)
hdv2003$qualif %>% is.na() %>% any()

Supprimer les observations avec des NA

Une chose qu'on peut vouloir faire avec les données manquantes, dans certains cas, est de tout simplement se débarrasser des lignes qui en contiennent. Pour ce faire, il suffit de réutiliser la fonction filter() et assigner le résultat à une variable (à noter ici qu'on aurait pu réutiliser la variable df, ce qui aurait eu pour effet d'écraser complètement les lignes qui contiennent des NA).
Ici, la seule chose qui change dans ce qu'on passe à filter() comparativement à l'exemple précédent est l'opérateur de négation !. La partie du code avant le symbole d'assignation <- (df %>% filter(!is.na(salaire))), pourrait être lu de la manière suivante : on demande à filter de renvoyer les lignes de df ou ce n'est pas le cas que la fonction is.na() retourne TRUE. Autrement dit, on demande à filter de renvoyer les lignes où is.na() renvoie FALSE.

df_sans_NA <- df %>% filter(!is.na(salaire))
df_sans_NA


Remplacer les NA par la moyenne

Une chose qu'il est commun de faire avec des données numériques est de remplacer les données manquantes par la moyenne des données qu'on a. Pour calculer la moyenne, il suffit d'utiliser la fonction mean() à laquelle on passe une colonne, comme on peut le voir ci-bas où calcule la moyenne de l'âge.

mean(df$age)
## [1] 46


La fonction mean() ne fonctionne cependant pas si certaines données sont des NA. Pas de panique, un paramètre nous permet d'enlever les NA lors du calcul de la moyenne, le paramètre na.rm (rm signifie remove). Ci-bas, on calcule donc la moyenne des salaires qui sont disponibles dans nos données.

mean(df$salaire, na.rm=TRUE)
## [1] 56666.67


Afin de pouvoir changer des éléments dans notre data.frame, nous aurons besoin de la fonction replace(). replace() prend trois arguments, d'abord la colonne (ou le vecteur) sur lequel on veut faire des changements, un vecteur de conditions, et la nouvelle valeur. (Ici, on inscrit chaque argument sur une nouvelle ligne pour faciliter la lisibilité du code, cela ne change rien au résultat du code).

replace(df$salaire, #La colonne
        df$salaire %>% is.na(), #La condition (doit être NA)
        mean(df$salaire, na.rm=TRUE)) #La moyenne sur les non NA
## [1] 40000.00 56666.67 56666.67 60000.00 70000.00


On a maintenant la colonne des salaires, où les deux NA ont été remplacés par la moyenne.
Comment peut-on faire maintenant pour changer les valeurs directement dans le data.frame? Pour cela il faut utiliser mutate(). Remarqué qu'ici, étant donné qu'on utilise le pipe (%>%), nous n'avons plus besoin de spécifier que salaire est une colonne de df en écrivant df$salaire. Il suffit d'écrire salaire dans la fonction replace()

df <- df %>% 
  mutate(salaire = replace(salaire, 
                           salaire %>% is.na(), 
                           mean(salaire, na.rm=TRUE)))

df

Exercice

Dans hdv2003 remplacez les NA de la colonne qualif par la valeur la plus fréquente. (Nous vous donnons la ligne de code qui permet de trouver la valeur la plus fréquente).

data(hdv2003)
valeur_la_plus_frequente <- hdv2003$qualif %>% table() %>% sort(decreasing = TRUE) %>% .[1] %>% names()
data(hdv2003)
valeur_la_plus_frequente <- hdv2003$qualif %>% table() %>% sort(decreasing = TRUE) %>% .[1] %>% names()
hdv2003 <- hdv2003 %>% 
  mutate(qualif = replace(qualif,
                          qualif %>% is.na(),
                          valeur_la_plus_frequente))


Retour à l'accueil