Search
Opérations sur les matrices

apply

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
1 4 7 10
2 5 8 11
3 6 9 12

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
0.00000001.386294 1.945910 2.302585
0.69314721.609438 2.079442 2.397895
1.09861231.791759 2.197225 2.484907

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
22
26
30

Si nous comparerons à la fonction rowSums que nous avons vue;

rowSums(m)
  1. 22
  2. 26
  3. 30

lapply

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)
$vecteur_1
1
$vecteur_2
17
$vecteur_3
43

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)
    1. 3
    2. 8
    3. 4
    4. 7
    5. 6
    1. 1
    2. 5
    3. 8
    4. 4
    5. 3
    6. 9
    1. 5
    2. 7
    3. 10
    4. 1
    5. 6
    6. 2
    7. 9
    1. 4
    2. 9
    3. 8
    4. 5
    5. 10
    6. 7
    7. 3
    8. 6

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)
vecteur_1
1
vecteur_2
17
vecteur_3
43

ou la somme de chaque élément de notre liste x

sapply(x, FUN = sum)
vecteur_1
1
vecteur_2
153
vecteur_3
3268

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))
    1. 6
    2. 10
    3. 3
    4. 2
    5. 9
    1. 10
    2. 7
    3. 9
    4. 1
    5. 3
    1. 8
    2. 2
    3. 3
    4. 9
    5. 1
sapply(x, sort)
2 11
3 32
6 73
9 98
10109

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.

Manipulation avec dplyr

le package dplyr

Dans ce cours, afin de manipuler les données, nous allons utiliser la librairie dplyr qui assure une manipulation plus intuitive des données. Toutefois, vous pouvez utiliser tout autre libraire ou même les fonctions de base de R.

# 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
kmtempsvitesseMoyennepuissanceMoyennebpm
1.244:01 19.1 160 134
4.849:42 30.2 133 146
1.021:57 30.8 141 139
17.6136:1129.2 125 144
9.2719:1029.0 121 143

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),]
kmtempsvitesseMoyennepuissanceMoyennebpm
5 9.2719:1029.0 121 143
417.6136:1129.2 125 144
2 4.849:42 30.2 133 146
3 1.021:57 30.8 141 139
1 1.244:01 19.1 160 134

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),]
kmtempsvitesseMoyennepuissanceMoyennebpm
3 1.021:57 30.8 141 139
2 4.849:42 30.2 133 146
417.6136:1129.2 125 144
5 9.2719:1029.0 121 143
1 1.244:01 19.1 160 134

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)
numeropoldebut_polfin_polfreq_paiementcout6cout7nbsinequipe
4 11-4-199610-4-199712 NA NA 0 3
4 11-4-199710-4-199812 NA NA 0 3
4 11-4-200217-7-200212 NA NA 0 3
4 11-4-200310-4-200412 NA NA 0 3
12 3-5-1995 2-5-1996 1 NA NA 0 3

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),]
numeropoldebut_polfin_polfreq_paiementcout6cout7nbsinequipe
9882006 13-6-199612-6-199712 NA NA 2 3
9021820 1-10-199630-9-1997 1 NA NA 2 3
8611733 10-5-19989-5-1999 1 NA NA 2 3
44 18-7-200210-4-200312 NA NA 0 3
54 11-4-200310-4-200412 NA NA 0 3

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

arrange

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)
numeropoldebut_polfin_polfreq_paiementcout6cout7nbsinequipe
71 15-2-1996 14-2-1997 1 NA NA 2 3
79 20-11-199721-6-1998 12 NA NA 2 3
116 4-9-1998 11-6-1999 1 NA NA 2 3
2036 14-3-200026-2-200112 NA NA 0 3
2036 27-2-200113-3-200112 NA NA 0 3

select

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)
numeropoltype_territoirenbsin
4 Semi-urbain0
4 Semi-urbain0
4 Semi-urbain0
2036 Semi-urbain0
2036 Semi-urbain1

filter

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')]
HorsepowerPassengers
102006
112955
181706
482785
522106

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')]
HorsepowerPassengers
2006
2955
1706
2785
2106

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")
numeropoldebut_polfin_polfreq_paiementcout6cout7nbsinequipe
71 15-2-199614-2-1997 1 NA NA 2 3
140 15-4-199514-4-199612 NA NA 2 3
1820 1-10-199630-9-1997 1 NA NA 2 3

mutate

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))
kmtempsvitesseMoyennepuissanceMoyennebpmarrondi
1.244:01 19.1 160 134 19
4.849:42 30.2 133 146 30
1.021:57 30.8 141 139 31
17.6136:1129.2 125 144 29
9.2719:1029.0 121 143 29

Ajoutons maintenant trois nouvlles variables;

mutate(df, arrondi=round(df$vitesseMoyenne,0), segementStrava=paste("segment",1:5,sep = "_"), arrondi_2=arrondi/2)
kmtempsvitesseMoyennepuissanceMoyennebpmarrondisegementStravaarrondi_2
1.24 4:01 19.1 160 134 19 segment_1 9.5
4.84 9:42 30.2 133 146 30 segment_215.0
1.02 1:57 30.8 141 139 31 segment_315.5
17.61 36:11 29.2 125 144 29 segment_414.5
9.27 19:10 29.0 121 143 29 segment_514.5

summarize

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))
TotalKmParcour
33.98
summarise(df,TotalKmParcour=sum(km), vitesseMoyenne= mean(df$vitesseMoyenne), puissanceMoyenne=mean(df$puissanceMoyenne))
TotalKmParcourvitesseMoyennepuissanceMoyenne
33.9827.66136

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;

L'opérateur Pipe: %>%

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.

Lorsque nous avons écrit:

select(df_ass, numeropol, type_territoire, nbsin)
numeropoltype_territoirenbsin
4 Semi-urbain0
4 Semi-urbain0
4 Semi-urbain0
2036 Semi-urbain0
2036 Semi-urbain1

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)
numeropoltype_territoirenbsin
4 Semi-urbain0
4 Semi-urbain0
4 Semi-urbain0
2036 Semi-urbain0
2036 Semi-urbain1

ou;

df_ass %>%
    select (numeropol,type_territoire, nbsin) %>%
    head
numeropoltype_territoirenbsin
4 Semi-urbain0
4 Semi-urbain0
4 Semi-urbain0
4 Semi-urbain0
12 Semi-urbain0

group_by

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
numeropoldebut_polfin_polfreq_paiementcout7nbsinequipecoutTot
4 11-4-199610-4-199712 NA 0 3 0
4 11-4-199710-4-199812 NA 0 3 0
4 11-4-200217-7-200212 NA 0 3 0
2036 27-2-200113-3-200112 NA 0 3 0.0
2036 14-3-200113-3-200212 NA 1 3 231051.8
summarise(df_ass,TotalNbSin=sum(nbsin), TotCout= sum((coutTot), na.rm = T))
TotalNbSinTotCout
156 1078791

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)
            )
type_territoireTotalNbSinTotCout
Rural 51 547105.01
Semi-urbain80 471157.64
Urbain 25 60528.81

Jointure des bases des données

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
nameprovincecompanylanguedate_naissanceageeage_permisnumeropol
Shane Robinson Nova Scotia May Ltd fr 1944-10-20 72 24 1
Courtney Nguyen Saskatchewan Foley, Moore and Mitchellen 1985-12-09 31 24 5
Lori Washington Yukon Territory Robinson-Reyes fr 1970-01-27 47 28 13
Heidi Freeman Northwest Territories Singh, Esparza and Santosen 1951-06-07 65 18 84
Morgan Buchanan Northwest Territories Rollins Inc fr 1971-07-31 45 31 91
df_auto <-read.csv("https://raw.githubusercontent.com/nmeraihi/data/master/cars_info.csv", header = T)
df_auto
numeropolmarque_voiturecouleur_voiturepresence_alarmelicense_plate
1 Autres Autre 0 DW 3168
5 RENAULTAutre 0 926 1RL
13 RENAULTAutre 1 SOV 828
84 HONDA Autre 0 CBV 102
91 BMW Autre 1 UOR-0725

Dans ces deux df, nous avons une colonne en commun numeropol

df_demo$numeropol
  1. 1
  2. 5
  3. 13
  4. 16
  5. 22
  6. 28
  7. 29
  8. 49
  9. 53
  10. 57
  11. 59
  12. 65
  13. 67
  14. 68
  15. 69
  16. 72
  17. 78
  18. 83
  19. 84
  20. 91
df_auto$numeropol
  1. 1
  2. 5
  3. 13
  4. 16
  5. 22
  6. 22
  7. 28
  8. 29
  9. 49
  10. 53
  11. 53
  12. 57
  13. 59
  14. 65
  15. 65
  16. 67
  17. 68
  18. 69
  19. 69
  20. 72
  21. 78
  22. 83
  23. 84
  24. 84
  25. 91

On peut voir l'index des lignes qui se trouvent dans les deux df

match(df_demo$numeropol, df_auto$numeropol)
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 7
  7. 8
  8. 9
  9. 10
  10. 12
  11. 13
  12. 14
  13. 16
  14. 17
  15. 18
  16. 20
  17. 21
  18. 22
  19. 23
  20. 25
df_demo$numeropol[match(df_demo$numeropol, df_auto$numeropol)]
  1. 1
  2. 5
  3. 13
  4. 16
  5. 22
  6. 29
  7. 49
  8. 53
  9. 57
  10. 65
  11. 67
  12. 68
  13. 72
  14. 78
  15. 83
  16. 91
  17. <NA>
  18. <NA>
  19. <NA>
  20. <NA>

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
  1. TRUE
  2. TRUE
  3. TRUE
  4. TRUE
  5. TRUE
  6. TRUE
  7. TRUE
  8. TRUE
  9. TRUE
  10. TRUE
  11. TRUE
  12. TRUE
  13. TRUE
  14. TRUE
  15. TRUE
  16. TRUE
  17. TRUE
  18. TRUE
  19. TRUE
  20. TRUE

ou le contraire maintenant

df_auto$numeropol %in% df_demo$numeropol
  1. TRUE
  2. TRUE
  3. TRUE
  4. TRUE
  5. TRUE
  6. TRUE
  7. TRUE
  8. TRUE
  9. TRUE
  10. TRUE
  11. TRUE
  12. TRUE
  13. TRUE
  14. TRUE
  15. TRUE
  16. TRUE
  17. TRUE
  18. TRUE
  19. TRUE
  20. TRUE
  21. TRUE
  22. TRUE
  23. TRUE
  24. TRUE
  25. TRUE

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
numeropolnameprovincecompanymarque_voiturecouleur_voiturepresence_alarmelicense_plate
1 Shane Robinson Nova Scotia May Ltd Autres Autre 0 DW 3168
5 Courtney Nguyen Saskatchewan Foley, Moore and MitchellRENAULT Autre 0 926 1RL
13 Lori Washington Yukon Territory Robinson-Reyes RENAULT Autre 1 SOV 828
84 Heidi Freeman Northwest Territories Singh, Esparza and SantosHONDA Autre 0 CBV 102
91 Morgan Buchanan Northwest Territories Rollins Inc BMW Autre 1 UOR-0725

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)
numeropolnameprovincecompanymarque_voiturecouleur_voiturepresence_alarmelicense_plate
1 Shane Robinson Nova Scotia May Ltd Autres Autre 0 DW 3168
5 Courtney Nguyen Saskatchewan Foley, Moore and MitchellRENAULT Autre 0 926 1RL
13 Lori Washington Yukon Territory Robinson-Reyes RENAULT Autre 1 SOV 828
84 Heidi Freeman Northwest Territories Singh, Esparza and SantosHONDA Autre 0 CBV 102
91 Morgan Buchanan Northwest Territories Rollins Inc BMW Autre 1 UOR-0725

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))
nameprovincecompanylanguemarque_voiturecouleur_voiturepresence_alarmelicense_plate
Shane Robinson Nova Scotia May Ltd fr Autres Autre 0 DW 3168
Courtney Nguyen Saskatchewan Foley, Moore and Mitchellen Autres Autre 0 DW 3168
Lori Washington Yukon Territory Robinson-Reyes fr Autres Autre 0 DW 3168
Jeffrey Garcia Nunavut Berger-Thompsonen Autres Autre 0 DW 3168
Colleen ColemanSaskatchewan Simmons-Smith en Autres Autre 0 DW 3168

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"))
numeropolnameprovincecompanymarque_voiturecouleur_voiturepresence_alarmelicense_plate
1 Shane Robinson Nova Scotia May Ltd Autres Autre 0 DW 3168
5 Courtney Nguyen Saskatchewan Foley, Moore and MitchellRENAULT Autre 0 926 1RL
13 Lori Washington Yukon Territory Robinson-Reyes RENAULT Autre 1 SOV 828
22 Jeffrey Garcia Nunavut Berger-ThompsonVOLKSWAGEN Autre 1 453 CFM
22 Jeffrey Garcia Nunavut Berger-ThompsonVOLKSWAGEN Autre 0 FHH 537

left_join

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
nombureaux
Gabriel5518
Adel 4538
NM 5518
Amine 4538
Mohamed4540
y<-data.frame(nom=c("Gabriel", "Adel", "JP", "Mathieu", "Amine"), 
              diplome=c("M.Sc", "Ph.D", "Ph.D", "Ph.D", "Ph.D"))
y
nomdiplome
GabrielM.Sc
Adel Ph.D
JP Ph.D
MathieuPh.D
Amine Ph.D
left_join(x,y,by = "nom")
Warning message:
“Column `nom` joining factors with different levels, coercing to character vector”
nombureauxdiplome
Gabriel5518 M.Sc
Adel 4538 Ph.D
NM 5518 NA
Amine 4538 Ph.D
Mohamed4540 NA

inner_join

Cette fonction permet de retourner seulement les éléments en commun des deux df

inner_join(x,y,by = "nom")
Warning message:
“Column `nom` joining factors with different levels, coercing to character vector”
nombureauxdiplome
Gabriel5518 M.Sc
Adel 4538 Ph.D
Mathieu5517 Ph.D
Amine 4538 Ph.D

semi_join

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")

anti_join

Cette fonction le contraire de la précédente

anti_join(x,y,by = "nom")