Le package plyr (1)

Auteur.e.s
Fabrice Dessaint, Inra
Résumé

Le package plyr est proposé par Hadley Wickham. Il offre de nombreuses fonctions permettant de manipuler et de traiter des objets R et plus particulièrement des tableaux (data.frame), des listes (list) et des matrices (array). Parmi toutes les fonctions proposées, on trouve un grand nombre de fonctions construites sur le modèle des fonctions apply(). Ces fonctions de type **ply() sont traitées dans une autre fiche technique ( Le package plyr (2)

Ici, nous allons nous intéresser à des fonctions moins connues mais tout aussi utiles.

Le package plyr

Le package plyr est proposé par Hadley Wickham. Il offre de nombreuses fonctions permettant de manipuler et de traiter des objets R et plus particulièrement des tableaux (data.frame), des listes (list) et des matrices (array). Parmi toutes les fonctions proposées, on trouve un grand nombre de fonctions construites sur le modèle des fonctions apply(). Ces fonctions de type **ply() seront traitées dans une autre fiche technique.

Ici, nous allons nous intéresser à des fonctions moins connues mais tout aussi utiles. Avant d’utiliser ces fonctions, il faut charger le package plyr.

library("plyr")

Note: Ce package n’est pas installé par défaut et il vous faudra peut être le faire avant de pouvoir l’utiliser.

install.packages("plyr")

Pour les différents exemples de code, on va utiliser 2 tableaux de données. Le premier est disponible dans le package ggplot2. Il a l’avantage de posséder un grand nombre d’observations.

data(diamonds, package = "ggplot2")

Le tableau diamonds contient 53940 lignes et 10 colonnes. Pour illustrer certaines spécificités, nous allons utiliser un autre tableau obtenu à partir du tableau diamonds. Dans ce tableau, on ne conserve qu’une partie des observations et pour cela on utilise la fonction subset().

reduit <- subset(diamonds, subset = color %in% c("J", "I") & cut %in% c("Fair", 
    "Good"))

Ce tableau ne contient que 1123 lignes.

Les fonctions d’analyse

Ces fonctions permettent d’appliquer (1) plusieurs fonctions à la même variable, (2) la même fonction à plusieurs variables, (3) plusieurs fonctions à plusieurs variables.

La fonction each()

Cette fonction permet d’appliquer un ensemble de fonctions à une même variable. Elle combine ces fonctions dans une fonction unique que l’on peut ensuite appliquer à une colonne quelconque d’un tableau et retourne les résultats sous la forme d’un vecteur nommé. Dans l’exemple suivant, on construit la fonction disper() qui calcule 2 mesures de dispersion: l’écart-type (sd) et l’écart absolu médian (mad) sur une variable.

disper <- each(sd, mad)
with(diamonds, disper(price))
##       sd      mad 
## 3989.440 2475.942

Ce code est équivalent à

disper.f <- function(x) c(sd = sd(x), mad = mad(x))
with(diamonds, disper.f(price))
##       sd      mad 
## 3989.440 2475.942

Les fonctions que l’on combine, ne doivent produire en sortie qu’un seul vecteur avec un ou plusieurs éléments. Lorsqu’il y a plusieurs éléments, le nom utilisé en sortie débute par le nom de la fonction les ayant produit.

resume <- each(disper, mean, median)
with(diamonds, resume(price))
##  disper.sd disper.mad       mean     median 
##   3989.440   2475.942   3932.800   2401.000

Note: on ne peut fournir aucune valeur pour des arguments supplémentaires; les fonctions sont utilisées avec les valeurs par défaut.

La fonction count()

Elle calcule les effectifs des différentes valeurs prises par une variable (qualitative ou non). Elle s’applique à un tableau et retourne en sortie un autre tableau contenant les différentes valeurs et leurs effectifs.

count(reduit, "cut")
##    cut freq
## 1 Fair  294
## 2 Good  829

Ce code est équivalent à

with(reduit, as.data.frame(table(cut)))
##         cut Freq
## 1      Fair  294
## 2      Good  829
## 3 Very Good    0
## 4   Premium    0
## 5     Ideal    0

où seules les valeurs ou combinaisons de valeurs dont l’effectif est non nul, seraient conservées.

On peut spécifier le nom de plusieurs variables pour avoir des effectifs croisés.

count(reduit, c("cut", "color"))
##    cut color freq
## 1 Fair     I  175
## 2 Fair     J  119
## 3 Good     I  522
## 4 Good     J  307

La fonction colwise()

Cette fonction généralise le principe des fonctions de type colMeans(), colSums() à d’autres fonctions que la moyenne ou la somme. Elle permet l’utilisation d’une fonction prévue pour traiter une variable, à l’ensemble des variables d’un tableau et retourne toujours un tableau.

Dans l’exemple suivant, on va utiliser la fonction is.numeric() pour identifier les variables numérique présentes dans le tableau (valeur TRUE).

type.n <- colwise(is.numeric)
type.n(reduit)
##   carat   cut color clarity depth table price    x    y    z
## 1  TRUE FALSE FALSE   FALSE  TRUE  TRUE  TRUE TRUE TRUE TRUE

Ce code est équivalent à

sapply(reduit, is.numeric)
##   carat     cut   color clarity   depth   table   price       x 
##    TRUE   FALSE   FALSE   FALSE    TRUE    TRUE    TRUE    TRUE 
##       y       z 
##    TRUE    TRUE

à la différence que la sortie produite par colwise() est un tableau (ici à une seule ligne).

Lorsque la fonction utilisée ne s’applique qu’à un type de variables (numérique, caractère, …) et que la variable testée n’est pas de ce type,

moyenne <- colwise(mean, na.rm = TRUE)
moyenne(reduit[, 4:6])
##   clarity    depth    table
## 1      NA 62.92484 58.87204

R affiche un message d’avis (Warning) et la valeur retournée est NA.

Deux autres fonctions plus spécialisées sont aussi proposées: catcolwise() qui traite uniquement les variables qualitatives et `numcolwise() qui traite uniquement les variables numériques.

Dans l’exemple qui suit, on crée une fonction qui calcule la moyenne des variables numériques du tableau:

moyenne <- numcolwise(mean)
moyenne(reduit)
##      carat    depth    table    price        x        y        z
## 1 1.120828 62.92484 58.87204 4868.496 6.388068 6.375156 4.012084

La fonction créée est équivalente à la fonction colMeans() avec cependant plusieurs différences. Elle ne s’applique qu’aux variables numériques du tableau, elle produit en sortie un tableau et elle accepte les arguments de la fonction mean().

Dans l’exemple précédent, la fonction utilisée (mean()) retournait un vecteur avec un seul élément. On avait donc en sortie un tableau avec une seule ligne. Mais la fonction peut aussi produire un tableau plus important.

Par exemple, si on voulait extraire les variables qualitatives d’un tableau, on pourrait faire

qual <- catcolwise(factor)
tmp <- qual(reduit)
str(tmp)
## 'data.frame':    1123 obs. of  3 variables:
##  $ cut    : Ord.factor w/ 2 levels "Fair"<"Good": 2 2 2 2 2 2 2 1 2 1 ...
##  $ color  : Ord.factor w/ 2 levels "I"<"J": 2 2 2 2 1 1 1 2 1 1 ...
##  $ clarity: Ord.factor w/ 8 levels "I1"<"SI2"<"SI1"<..: 2 3 3 3 2 3 4 2 2 3 ...

Note Seuls les niveaux effectivement présents ont été conservés: 2 niveaux pour la variable cut au lieu des 5 initiaux.

La fonction summarise()

Cette fonction permet de construire des tableaux dont les variables sont le résultat de l’application de fonctions aux variables d’un tableau initial. Son utilisation est très simple: on précise le nom du tableau contenant les données à analyser, on choisit la fonction à appliquer et la variable à traiter et on donne un nom à la colonne résultat. Dans l’exemple qui suit, on mime le comportement de la fonction colMeans().

summarise(reduit, x = mean(x), y = mean(y), z = mean(z))
##          x        y        z
## 1 6.388068 6.375156 4.012084

Evidemment, l’intérêt de la fonction résulte du fait que l’on peut utiliser un éventail plus large de fonctions avec cependant la contrainte que les fonctions utilisées renvoient des vecteurs de même taille. Dans l’exemple qui suit, on utilise la fonction disper().

result <- summarise(reduit, x = disper(x), y = disper(y), z = disper(z))
rownames(result) <- c("sd", "mad")
result
##            x        y         z
## sd  1.145820 1.134254 0.7184739
## mad 1.126776 1.141602 0.7264740

Les fonctions sur des data.frames

Ces fonctions permettent de modifier un tableau, soit en réordonnant les lignes, soit en renommant des variables, soit en créant de nouvelles variables.

La fonction arrange()

Cette fonction permet de réordonner les lignes d’un tableau selon les valeurs d’une ou de plusieurs variables. Son utilisation est plus simple que le passage par la fonction order().

arrange(reduit, price)[1:5, ]
##   carat  cut color clarity depth table price    x    y    z
## 1  0.31 Good     J     SI2  63.3    58   335 4.34 4.35 2.75
## 2  0.30 Good     J     SI1  64.0    55   339 4.25 4.28 2.73
## 3  0.30 Good     J     SI1  63.4    54   351 4.23 4.29 2.70
## 4  0.30 Good     J     SI1  63.8    56   351 4.23 4.26 2.71
## 5  0.30 Good     I     SI2  63.3    56   351 4.26 4.30 2.71

Pour ordonner les valeurs selon un ordre décroissant, on utilisera la fonction desc().

arrange(reduit, cut, color, clarity, desc(price))[1:10, ]
##    carat  cut color clarity depth table price    x    y    z
## 1   3.02 Fair     I      I1  65.2    56 10577 9.11 9.02 5.91
## 2   2.20 Fair     I      I1  66.3    56  7934 8.05 8.00 5.32
## 3   1.96 Fair     I      I1  66.8    55  6147 7.62 7.60 5.08
## 4   1.51 Fair     I      I1  65.1    57  5750 7.16 7.12 4.65
## 5   2.01 Fair     I      I1  67.4    58  5696 7.71 7.64 5.17
## 6   2.01 Fair     I      I1  55.9    64  5696 8.48 8.39 4.71
## 7   2.00 Fair     I      I1  66.0    60  5667 7.78 7.74 5.10
## 8   1.51 Fair     I      I1  65.7    61  5074 7.08 7.02 4.63
## 9   1.50 Fair     I      I1  65.1    59  5040 7.12 6.96 4.58
## 10  1.50 Fair     I      I1  66.1    57  4704 7.12 7.04 4.68

La fonction rename()

Cette fonction permet de renommer une ou plusieurs variables d’un tableau ou plus généralement d’un objet ayant un attribut names.

# data.frame
tmp <- summarise(reduit, x = mean(x), y = mean(y), z = mean(z))
rename(tmp, replace = c(x = "Moyenne.x"))
##   Moyenne.x        y        z
## 1  6.388068 6.375156 4.012084
# vecteur
z <- c(a = 1, b = 6, c = 4)
rename(z, replace = c(a = "A", b = ""))
## A   c 
## 1 6 4
# liste
z <- list(Obj1 = 1, Obj2 = "Essai", Obj3 = matrix(1:4, ncol = 2))
rename(z, replace = c(Obj1 = "OBJ1", Obj2 = ""))
## $OBJ1
## [1] 1
## 
## [[2]]
## [1] "Essai"
## 
## $Obj3
##      [,1] [,2]
## [1,]    1    3
## [2,]    2    4

La fonction mutate()

Cette fonction s’apparente à la fonction transform() mais elle est d’utilisation plus générale (et plus rapide d’exécution). Elle permet de transformer des variables ou d’en créer de nouvelles. Dans l’exemple suivant, on crée une variable volume.

tmp <- mutate(reduit, volume = x * y * z)
head(tmp)
##    carat  cut color clarity depth table price    x    y    z   volume
## 5   0.31 Good     J     SI2  63.3    58   335 4.34 4.35 2.75 51.91725
## 11  0.30 Good     J     SI1  64.0    55   339 4.25 4.28 2.73 49.65870
## 18  0.30 Good     J     SI1  63.4    54   351 4.23 4.29 2.70 48.99609
## 19  0.30 Good     J     SI1  63.8    56   351 4.23 4.26 2.71 48.83366
## 21  0.30 Good     I     SI2  63.3    56   351 4.26 4.30 2.71 49.64178
## 60  0.30 Good     I     SI1  63.2    55   405 4.25 4.29 2.70 49.22775

Ce code est équivalent à

tmp <- transform(reduit, volume = x * y * z)

L’un des intérêts de la fonction réside dans le fait que le processus de transformation/création est itératif et que de ce fait, les variables créées ou transformées sont utilisables pour créer/transformer d’autres variables.

Dans l’exemple suivant, on crée une variable volume qui est ensuite utilisée dans la création de la variable ratio qui est elle même ensuite transformée.

tmp <- mutate(reduit, volume = x * y * z, ratio = carat/volume, ratio = 100 * 
    ratio)
head(tmp[, c("volume", "ratio")])
##      volume       ratio
## 5  51.91725 0.005971040
## 11 49.65870 0.006041237
## 18 48.99609 0.006122938
## 19 48.83366 0.006143304
## 21 49.64178 0.006043297
## 60 49.22775 0.006094124

Extraction et jointures

Les fonctions suivantes permettent d’extraire des observations d’un tableau ou d’ajouter des observations/variables à un tableau.

La fonction match_df()

Cette fonction permet d’extraire d’un tableau un ensemble de lignes correspondant à des critères de filtres. Les filtres sont définis dans un autre tableau. Par exemple, le tableau reduit que l’on a construit en début de fiche avec la fonction subset() aurait pu être produit par:

(filtre <- data.frame(expand.grid(color = c("I", "J"), cut = c("Fair", 
    "Good"))))
##   color  cut
## 1     I Fair
## 2     J Fair
## 3     I Good
## 4     J Good
reduit <- match_df(diamonds, filtre)
## Matching on: cut, color
dim(reduit)
## [1] 1123   10

Dans l’exemple suivant, on crée un filtre avec 2 lignes et 4 variables et on veut extraire les lignes du tableau diamonds qui correspondent au filtre.

filtre <- data.frame(cut = c("Fair", "Ideal"), color = c("J", "D"), clarity = c("VVS2", 
    "SI1"), table = 56)
filtre
##     cut color clarity table
## 1  Fair     J    VVS2    56
## 2 Ideal     D     SI1    56
tmp <- match_df(diamonds, filtre)
## Matching on: cut, color, clarity, table
tmp[1:10, ]
##     carat   cut color clarity depth table price    x    y    z
## 64   0.30 Ideal     D     SI1  62.1    56   552 4.30 4.33 2.68
## 351  0.72 Ideal     D     SI1  62.2    56  2804 5.74 5.77 3.58
## 499  0.70 Ideal     D     SI1  60.7    56  2822 5.75 5.72 3.48
## 501  0.71 Ideal     D     SI1  60.2    56  2822 5.86 5.83 3.52
## 507  0.70 Ideal     D     SI1  61.8    56  2822 5.73 5.63 3.51
## 661  0.79 Ideal     D     SI1  61.5    56  2846 5.96 5.91 3.65
## 768  0.73 Ideal     D     SI1  61.5    56  2858 5.84 5.80 3.58
## 805  0.71 Ideal     D     SI1  60.8    56  2863 5.80 5.77 3.52
## 868  0.70 Ideal     D     SI1  61.8    56  2872 5.63 5.73 3.51
## 871  0.70 Ideal     D     SI1  60.7    56  2872 5.72 5.75 3.48

Le filtre peut être construit manuellement ou résulter d’une série d’instructions R.

La fonction join()

Cette fonction permet d’effectuer des jointures entre 2 tableaux.

  • Jointure interne: jointure n’affichant que les lignes ayant une correspondance dans les deux tables
  • Jointure externe: jointure incluant des lignes même si ces dernières ne correspondent pas à des lignes de la table jointe. Pour spécifier les lignes à inclure, vous disposez de trois types de jointures externes
    • Jointure externe gauche: la jointure inclut toutes les lignes du premier tableau. Les lignes sans correspondance dans l’autre tableau n’apparaissent pas.
    • Jointure externe droite: la jointure inclut toutes les lignes du deuxième tableau. Les lignes sans correspondance dans le premier tableau ’apparaissent pas.
    • Jointure externe entière: Inclut toutes les lignes des 2 tableaux joints, qu’elles aient ou non une correspondance entre elles.
a <- data.frame(x = seq(0, 10, 2), a = round(runif(6), 2))
b <- data.frame(x = seq(0, 12, 3), b = letters[1:5])

L’argument type permet d’indiquer le type de jointures. Par défaut, c’est une jointure externe gauche qui est réalisée.

join(x = a, y = b, by = "x", type = "left")
##    x    a    b
## 1  0 0.98    a
## 2  2 0.93 <NA>
## 3  4 0.73 <NA>
## 4  6 0.72    c
## 5  8 0.28 <NA>
## 6 10 0.03 <NA>

Seules les informations relatives aux observations de b qui ont une correspondance dans a (ici x=0 et x=6) sont ajoutées. Le tableau final a autant d’observations que le tableau a.

Dans le cas d’une jointure externe droite, on a l’inverse: seules les informations relatives aux observations de a qui ont une correspondance dans b (ici x=0 et x=6) sont ajoutées. Le tableau final a autant d’observations que le tableau b.

join(x = a, y = b, by = "x", type = "right")
##    x    a b
## 1  0 0.98 a
## 2  3   NA b
## 3  6 0.72 c
## 4  9   NA d
## 5 12   NA e

Enfin,la jointure externe complète inclut toutes les lignes des 2 tableaux joints. On a 6 + 5 lignes moins les 2 communes, donc 9 lignes dans le tableau final.

join(x = a, y = b, by = "x", type = "full")
##    x    a    b
## 1  0 0.98    a
## 2  2 0.93 <NA>
## 3  4 0.73 <NA>
## 4  6 0.72    c
## 5  8 0.28 <NA>
## 6 10 0.03 <NA>
## 7  3   NA    b
## 8  9   NA    d
## 9 12   NA    e

La jointure interne n’affiche que les lignes ayant une correspondance dans les deux tables; soit 2 observations.

join(x = a, y = b, by = "x", type = "inner")
##   x    a b
## 1 0 0.98 a
## 2 6 0.72 c

La fonction join_all()

Effectue des jointures entre plusieurs tableaux. Les jointures sont réalisées de façon récursive.

dfs <- list(a = a, b = b, c = data.frame(x = seq(0, 12, 4), c = LETTERS[1:4]))

L’argument type permet d’indiquer le type de jointures. Par défaut, c’est une jointure externe gauche qui est réalisée.

join_all(dfs)
## Joining by: x
## Joining by: x
##    x    a    b    c
## 1  0 0.98    a    A
## 2  2 0.93 <NA> <NA>
## 3  4 0.73 <NA>    B
## 4  6 0.72    c <NA>
## 5  8 0.28 <NA>    C
## 6 10 0.03 <NA> <NA>

On peut faire une jointure externe droite

join_all(dfs, type = "right")
## Joining by: x
## Joining by: x
##    x    a    b c
## 1  0 0.98    a A
## 2  4   NA <NA> B
## 3  8   NA <NA> C
## 4 12   NA    e D

Une jointure externe complète

join_all(dfs, type = "full")
## Joining by: x
## Joining by: x
##    x    a    b    c
## 1  0 0.98    a    A
## 2  2 0.93 <NA> <NA>
## 3  4 0.73 <NA>    B
## 4  6 0.72    c <NA>
## 5  8 0.28 <NA>    C
## 6 10 0.03 <NA> <NA>
## 7  3   NA    b <NA>
## 8  9   NA    d <NA>
## 9 12   NA    e    D

Une jointure interne

join_all(dfs, type = "inner")
## Joining by: x
## Joining by: x
##   x    a b c
## 1 0 0.98 a A

Pour aller plus loin

H. Wickham (2011). The Split-Apply-Combine Strategy for Data Analysis. Journal of Statistical Software, 40(1).

Versions des outils utilisés
R version 3.4.2 (2017-09-28), plyr version 1.8.4
Thèmes de la fiche