Nous avons vu qu'il était possible de faire des opérations sur les matrices en ligne ou en colonne. Toutefois, ce n'est pas toutes les fonctions statistiques qui sont applicables sur des colonnes et/ou lignes comme colMeans
. Pour appliquer d'utres sortes de fonctions, nous devons utiliser la fonction apply
.
On peut alors utiliser apply
lorsqu'on veut appliquer un calcule ou une fonction quelconque (FUN) sur des colonnes ou des lignes d'une matrice (incluant les matrices plus que 2D)
Soit une matrice de 12 premiers entiers;
m<-matrix(1:12, nrow=3)
m
Calculons le logarithme naturel de chaque élément de cette matrice:
h<-apply(m, c(1,2), log) #c(1,2) ça veut dire sur ligne et colonne
h
Créons une matrice 3 x 1
qui nous retourne le résultat de la somme de chaque ligne;
h<-matrix(apply(m, 1, sum))
h
Si nous comparerons à la fonction rowSums
que nous avons vue;
rowSums(m)
La fonction lapply applique une fonction quelconque (FUN) à tous les éléments d’un vecteur ou d’une liste X et retourne le résultat sous forme de liste.
Dans l'exemple suivant, nous avons une liste de trois vecteurs {vecteur_1, vecteur_2, vecteur_3} de taille différente, on voudrait savoir quelle est la taille de chaque élément, on voudrait la réponse dans une liste;
x <- list(vecteur_1 = 1, vecteur_2 = 1:17, vecteur_3 = 55:97)
lapply(x, FUN = length)
Dans le résultat ci-haut, la fonction lapply
nous a retourné une liste de trois éléments avec la taille de chaque vecteur.
Regardons un autre exemple où nous cherchons à créer quatre échantillons aléatoires de taille {5, 6, 7, 8} tirés du vecteur x= 1 2 3 4 5 6 7 8 9 10
set.seed(123)
lapply(5:8, sample, x = 1:10)
sapply
Dans certains cas, on voudrait appliquer une fonction quelconque sur une liste, mais on ne veut pas que R
nous retourne une une liste, on désire plutôt que R
nous retourne un vecteur. La fonction sapply
fait exactement cela. Le résultat est donc simplifiée par rapport à celui de lapply
, d’où le nom de la fonction.
La taille de chaque élément de notre liste;
sapply(x, FUN = length)
ou la somme de chaque élément de notre liste x
sapply(x, FUN = sum)
Si le résultat de chaque application de la fonction est un vecteur et que les vecteurs sont tous de la même longueur, alors sapply retourne une matrice, remplie comme toujours par colonne :
(x <- lapply(rep(5, 3), sample, x = 1:10))
sapply(x, sort)
autre
Il existe d'autres façons de manipuler les matrices, listes, vecteurs ...etc. Dans ce cours nous avons couvert les trois principaux, toutefois, on peut avoir besoin dans certains cas d'utiliser vapply
, mapply
, Map
, rapply
ou même tapply
qui s'apparentent tous aux trois fonctions que nous avons couverts avec plus d'options ou format différent du résultat obtenu.
# install.packages("dplyr")
D'abord, téléchargeons un petit df afin d'illustrer la théorie. Dans ce df, nous avons les données d'un cycliste qui est sorti un jour d'été faire un petit tour dans l'île de Montréal. Chaque observation, représente le nombre de km parcourus d'un parcours (lap), le temps que ça a pris, la vitesse moyenne en km/h
de chaque lap, la puissance moyenne en watts
et finalement, les battements de cours par minutes.
df<-read.csv("https://raw.githubusercontent.com/nmeraihi/data/master/exemple_2.txt")
df
Il possible d'ordonner les données avec la fonction de base de R
appelé order
. Par exemple, on voudrait ordonner notre df en ordre croissant sur la variable puissanceMoyenne
df[order(df$puissanceMoyenne),]
Par défaut, l'ordre est est croissant, on peut le rendre décroissant en ajoutant l'argument decreasing = T
df[order(df$vitesseMoyenne, decreasing = T),]
Toutefois, lorsque nous avons une base de données comportant un nombre plus important de variables, la syntaxe peut devenir plus compliquée et lourde d’écriture. Regardons un autre exemple:
df_ass <-read.csv("https://raw.githubusercontent.com/nmeraihi/data/master/assurance.csv", header = T)
head(df_ass)
Affichons notre base de données en ordre croissant sur le nombre de sinistres et le numéro de police;
df_ass[order(df_ass$nbsin, df_ass$numeropol, decreasing = T),]
Dans le tableau affiché ci-haut, on voit bien que cette fonction ne nous permet pas d'appliquer un ordre croissant ou décroissant sur une variable précise
Maintenant, utilisons le paquet (package) dplyr
qui nous permet de plus facilement d'appliquer un ordre quelconque sur une variable précise indépendamment des autres variables;
library(dplyr, warn.conflicts = FALSE)
arrange(df_ass, desc(nbsin), numeropol)
Ce paquet nous permet aussi de sélectionner des variables d'intérêt. Par exemple, dans notre df_ass
, on désire seulement sélectionner les variables numeropol
, type_territoire
et nbsin
select(df_ass, numeropol,type_territoire, nbsin)
Afin de filtrer des données sur des observations d'intérêt. On peut utiliser la fonction de base de R which
. Par exemple dans les données Cars93
du package MASS
, on voudrait extraire les véhicules ayant 8 cylindres. On voudrait également afficher que les deux variables 'Horsepower'
et 'Passengers'
library(MASS, warn.conflicts = F)
Cars93[which(Cars93$Cylinders==8), c('Horsepower' , 'Passengers')]
Toutefois, la fonction filter
de la librairie dyplr
est plus flexible lorsqu'il s'agit d'appliquer des filtres plus complexes. Essayons le même exemple avec cette fonction;
filter(Cars93, Cylinders==8)[c('Horsepower' , 'Passengers')]
Si l'on cherche les médecins qui ont eu deux sinistres dans notre base de données df_ass
;
filter(df_ass, nbsin==2, type_prof=="Médecin")
Dans ce package, on trouve aussi la fonction mutate
qui permet d'ajouter de nouvelles variables à notre df
mutate(df, arrondi=round(df$vitesseMoyenne,0))
Ajoutons maintenant trois nouvlles variables;
mutate(df, arrondi=round(df$vitesseMoyenne,0), segementStrava=paste("segment",1:5,sep = "_"), arrondi_2=arrondi/2)
La fonction summarize
est très similaire à la fonction mutate
. Toutefois, contrairement à mutate
, la fonction summarize
ne travaille pas sur une copie du df, mais elle crée un tout nouveau df avec les nouvelles variables.
summarise(df,TotalKmParcour=sum(km))
summarise(df,TotalKmParcour=sum(km), vitesseMoyenne= mean(df$vitesseMoyenne), puissanceMoyenne=mean(df$puissanceMoyenne))
On voit bien que l'écriture du code commence à être un peu plus compliquée lorsque nous avons plusieurs parenthèses dans notre fonction. Pour remédier à ce problème, nous verrons la notion de piiping;
Avant d'aller plus loin, introduisons l'opérateur de pipe:%>%. dplyr importe cet opérateur d'une autre librairie (magrittr
). Cet opérateur vous permet de diriger la sortie d'une fonction vers l'entrée d'une autre fonction. Au lieu d'imbriquer des fonctions (lecture de l'intérieur vers l'extérieur), l'idée de piping est de lire les fonctions de gauche à droite.
Crédit de l'image Pipes in R Tutorial For Beginners
Lorsque nous avons écrit:
select(df_ass, numeropol, type_territoire, nbsin)
Si on lit ce que nous avons écrit précédemment de l'intérieur vert l'extérieur, en utilisant le piping, nous aurons ceci:
df_ass %>%
select (numeropol,type_territoire, nbsin)
ou;
df_ass %>%
select (numeropol,type_territoire, nbsin) %>%
head
Nous pouvons aussi grouper les données comme nous le faisions dans SAS
avec les PROC SQL
df_ass$coutTot<-rowSums(df_ass[,c(19:25)], na.rm = T, dims = 1)
df_ass
summarise(df_ass,TotalNbSin=sum(nbsin), TotCout= sum((coutTot), na.rm = T))
Cherchons par exemple nombre de sinistres totaux ainsi que leurs coûts par territoire. En utilisant la syntaxe du piping, ça devient plus facile d'inclure plus de sous-groupes;
df_ass %>%
group_by(type_territoire) %>%
summarise(TotalNbSin=sum(nbsin),
TotCout= sum((coutTot), na.rm = T)
)
Dans cette section, nous allons joindre deux ou plusieurs df. Mais d'abord importons deux df afin illustrer quelques exemples;
df_demo <-read.csv("https://raw.githubusercontent.com/nmeraihi/data/master/donnes_demo.csv", header = T)
df_demo
df_auto <-read.csv("https://raw.githubusercontent.com/nmeraihi/data/master/cars_info.csv", header = T)
df_auto
Dans ces deux df, nous avons une colonne en commun numeropol
df_demo$numeropol
df_auto$numeropol
On peut voir l'index des lignes qui se trouvent dans les deux df
match(df_demo$numeropol, df_auto$numeropol)
df_demo$numeropol[match(df_demo$numeropol, df_auto$numeropol)]
On peut aussi faire un test logique sur la présence des observations du df_demo dans df_auto;
df_demo$numeropol %in% df_auto$numeropol
ou le contraire maintenant
df_auto$numeropol %in% df_demo$numeropol
Dans ce cas toutes les variables se trouvent dans les deux df
merge(df_demo,df_auto, by.x = "numeropol", by.y = "numeropol") # x est le df_demo et y est le df df_auto
Que serait-il arrivé si l’on n'avait pas spécifié les arguments by.x = "numeropol", by.y = "numeropol"
?
merge(df_demo,df_auto)
Cela a bien fonctionné, car R a automatiquement trouvé les noms de colonnes communs au deux df;
Maintenant, changeons les noms de colonnes et voyons ce qui arrive
names(df_auto)[names(df_auto)=="numeropol"] <- "auto_numpol"
Bien évidemment, cela crée une jointure croisée comme on l'avait vu dans les cours de SAS
head(merge(df_demo,df_auto))
On vient bien que dans la dernière colonne license_plate
, nous obtenons la même observation ce qui est clairement une erreur;
Corrigeons le problème;
head(merge(df_demo,df_auto, by.x = "numeropol", by.y = "auto_numpol"))
la fonction left_join
prend toute l'information de gauche et l'information existante de la partie droite qui est basée sur le critère en commun
x<-data.frame(nom=c("Gabriel", "Adel", "NM", "Mathieu", "Amine", "Mohamed"),
bureaux=c("5518", "4538", "5518", "5517", "4538", "4540"))
x
y<-data.frame(nom=c("Gabriel", "Adel", "JP", "Mathieu", "Amine"),
diplome=c("M.Sc", "Ph.D", "Ph.D", "Ph.D", "Ph.D"))
y
left_join(x,y,by = "nom")
Cette fonction permet de retourner seulement les éléments en commun des deux df
inner_join(x,y,by = "nom")
Cette fonction retourne seulement les éléments du premier df qui se retrouve dans le deuxième df, sans nous retourner les éléments de ce dernier
semi_join(x,y,by = "nom")
Cette fonction le contraire de la précédente
anti_join(x,y,by = "nom")