Format wide et long : pourquoi, et comment ?

1. Les formats  wide et long

 1.1 Format  wide:

Je dis très régulièrement que, pour être traités avec le logiciel R, les jeux de données doivent être au format “tidy”, c’est-à-dire avec une ligne par observation, une variable par colonne, et une valeur au croisement d’une ligne et d’une colonne.

format wide et lon avec R

Le format tidy, d’après  R for Data Science.

Cette appelation “tidy” a été donnée assez récemment par Hadley Wickham.

Il existe deux grands type de format tidy, le format wide et le fomat long.

Par exemple le jeu de données suivant, dérivé du jeu de données iris, est au format wide.

 
    my_iris

    ##   id Sepal.Length Sepal.Width Petal.Length Petal.Width
    ## 1  1          5.1         3.5          1.4         0.2
    ## 2  2          4.9         3.0          1.4         0.2
    ## 3  3          4.7         3.2          1.3         0.2
    ## 4  4          4.6         3.1          1.5         0.2
    ## 5  5          5.0         3.6          1.4         0.2
 

1.2 Le format long

Bien que le format “wide” soit le format le plus adéquat pour traiter les données, parfois, il peut être utile de passer nos jeux de données en format “long”. Dans ce format, le jeu de données est réduit à trois colonnes :

  • une contenant l’identifiant de l’observation (pas forcément nécessaire et dans ce cas,le jeu de données n’a que les deux variables suivantes)
  • une (ici nommée “variable”) contenant le nom de la variable considérée,
  • une (ici nommée “value”) contenant la valeur.
Par exemple, le jeu de données précédent, passé en format long, est comme cela :
my_iris_long

    ##    id     variable value
    ## 1   1 Sepal.Length   5.1
    ## 2   2 Sepal.Length   4.9
    ## 3   3 Sepal.Length   4.7
    ## 4   4 Sepal.Length   4.6
    ## 5   5 Sepal.Length   5.0
    ## 6   1  Sepal.Width   3.5
    ## 7   2  Sepal.Width   3.0
    ## 8   3  Sepal.Width   3.2
    ## 9   4  Sepal.Width   3.1
    ## 10  5  Sepal.Width   3.6
    ## 11  1 Petal.Length   1.4
    ## 12  2 Petal.Length   1.4
    ## 13  3 Petal.Length   1.3
    ## 14  4 Petal.Length   1.5
    ## 15  5 Petal.Length   1.4
    ## 16  1  Petal.Width   0.2
    ## 17  2  Petal.Width   0.2
    ## 18  3  Petal.Width   0.2
    ## 19  4  Petal.Width   0.2
    ## 20  5  Petal.Width   0.2
  Voici un schéma pour visualiser la transformation du format wide vers le format long :

2. Pourquoi utiliser le format long ?

Parce que parfois, c’est plus pratique de disposer du jeu de données en format long.

2.1 Facilité pour calculer des paramètres

Par exemple pour calculer des paramètres descriptifs pour toutes les variables, avec la fonction summarise() du package dplyr. Il suffit alors d’employer group_by(variable), comme ceci :
    library(tidyverse)
    my_iris_long %>%
        group_by(variable) %>%
        summarise(N=n(), 
                  moy=mean(value, na.rm=TRUE),
                  med=median(value,na.rm=TRUE),
                  ecart_type=sd(value,na.rm=TRUE))

    
    ## # A tibble: 4 x 5
    ##   variable         N   moy   med ecart_type
    ##   <chr>        <int> <dbl> <dbl>      <dbl>
    ## 1 Petal.Length     5  1.4    1.4     0.0707
    ## 2 Petal.Width      5  0.2    0.2     0     
    ## 3 Sepal.Length     5  4.86   4.9     0.207 
    ## 4 Sepal.Width      5  3.28   3.2     0.259
 

2.2 Facilité pour faire du faceting avec ggplot2

Par exemple, si je veux visualiser la densité de distribution des variables Sepal.Length, Sepal.Width, Petal.Length , et Petal.Width du jeu de données “iris”.

Avec le format tidy, il faut faire le plot variable par variable, ou bien faire une boucle. Mais quand on débute avec R, faire des boucles avec des ggplots ce n’est pas la chose la plus simple !

  ggplot(iris, aes(Sepal.Length)) +
        geom_density(fill="blue")
  facettng avec ggplot2 Alors que si on a un jeu de données en format long, il suffit d’inclure la variable “variable” dans la fonction facet_wrap(), ou facet_grid() , comme cela:
  iris_long <- iris %>%
        select(-Species) %>%
        gather(variable, value)

    ggplot(iris_long, aes(value)) +
        geom_density(fill="blue")+
        facet_wrap(~variable)
      facetting avec ggplot2  

3. Passer du format wide au format long

Il est très facile de transformer un jeu de données au format wide vers un format long. Pour cela, on utilise la fonction gather() du package tidyr, qui fait parti du super package tidyverse.

Voici un exemple avec le jeu de données “iris” :
    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

    iris_long <- iris %>%
        gather(variable, value) 

    ## Warning: attributes are not identical across measure variables;
    ## they will be dropped
 

Ici, on obtient un warning, car le jeu de données “iris” comporte une variable catégorielle (Species). Dans ce cas, on peut indiquer à la fonction gather(), de ne pas considérer la variable “Species”, en utilisant le signe – (moins), comme cela :

iris_long <- iris %>%
gather(variable, value,-Species)
 

Dans ce cas, une troisième variable est créée et comporte le nom de l’espèce à laquelle se rapporte chacune des lignes :

iris_long

    ##        Species     variable value
    ## 1       setosa Sepal.Length   5.1
    ## 2       setosa Sepal.Length   4.9
    ## 3       setosa Sepal.Length   4.7
    ## 4       setosa Sepal.Length   4.6
    ## 5       setosa Sepal.Length   5.0
    ......................................
    ## 51  versicolor Sepal.Length   7.0
    ## 52  versicolor Sepal.Length   6.4
    ## 53  versicolor Sepal.Length   6.9
    ## 54  versicolor Sepal.Length   5.5
    ## 55  versicolor Sepal.Length   6.5
    ....................................... 
    ## 101  virginica Sepal.Length   6.3
    ## 102  virginica Sepal.Length   5.8
    ## 103  virginica Sepal.Length   7.1
    ## 104  virginica Sepal.Length   6.3
    ## 105  virginica Sepal.Length   6.5
    ....................................... 
    ## 151     setosa  Sepal.Width   3.5
    ## 152     setosa  Sepal.Width   3.0
    ## 153     setosa  Sepal.Width   3.2
    ## 154     setosa  Sepal.Width   3.1
    ## 155     setosa  Sepal.Width   3.6
    ....................................... 
    ## 201 versicolor  Sepal.Width   3.2
    ## 202 versicolor  Sepal.Width   3.2
    ## 203 versicolor  Sepal.Width   3.1
    ## 204 versicolor  Sepal.Width   2.3
    ## 205 versicolor  Sepal.Width   2.8
    ## 206 versicolor  Sepal.Width   2.8
    ....................................... 
    ## 251  virginica  Sepal.Width   3.3
    ## 252  virginica  Sepal.Width   2.7
    ## 253  virginica  Sepal.Width   3.0
    ## 254  virginica  Sepal.Width   2.9
    ## 255  virginica  Sepal.Width   3.0
    ....................................... 
    ## 301     setosa Petal.Length   1.4
    ## 302     setosa Petal.Length   1.4
    ## 303     setosa Petal.Length   1.3
    ## 304     setosa Petal.Length   1.5
    ## 305     setosa Petal.Length   1.4
    
    ## 351 versicolor Petal.Length   4.7
    ## 352 versicolor Petal.Length   4.5
    ## 353 versicolor Petal.Length   4.9
    ## 354 versicolor Petal.Length   4.0
    ## 355 versicolor Petal.Length   4.6
    ....................................... 
    ....................................... 
    ## 595  virginica  Petal.Width   2.5
    ## 596  virginica  Petal.Width   2.3
    ## 597  virginica  Petal.Width   1.9
    ## 598  virginica  Petal.Width   2.0
    ## 599  virginica  Petal.Width   2.3
    ## 600  virginica  Petal.Width   1.8
   

4. Quelques exemples de calcul de paramètres descriptifs

Comme expliqué précédemment, on utilise la variable nommée “variable” en argument de la fonction group_by(), avant d’utiliser la fonction summarise(), comme ceci:

 iris_long %>%
        group_by(variable) %>%
        summarise(N=n(),
                  moy=mean(value, na.rm=TRUE),
                  med=median(value, na.rm=TRUE),
                  sd=sd(value, na.rm=TRUE),
                  se=sd/sqrt(N),
                  ci=qt(0.975,N-1)*se)

    ## # A tibble: 4 x 7
    ##   variable         N   moy   med    sd     se     ci
    ##   <chr>        <int> <dbl> <dbl> <dbl>  <dbl>  <dbl>
    ## 1 Petal.Length   150  3.76  4.35 1.77  0.144  0.285 
    ## 2 Petal.Width    150  1.20  1.3  0.762 0.0622 0.123 
    ## 3 Sepal.Length   150  5.84  5.8  0.828 0.0676 0.134 
    ## 4 Sepal.Width    150  3.06  3    0.436 0.0356 0.0703
  Et si on veut calculer ces mêmes paramètres mais en distinguant les espèces, rien de plus simple : on ajoute la variable Species dans la fonction group_by() :
 iris_long %>%
        group_by(variable, Species) %>%
        summarise(N=n(),
                  moy=mean(value, na.rm=TRUE),
                  med=median(value, na.rm=TRUE),
                  sd=sd(value, na.rm=TRUE),
                  se=sd/sqrt(N),
                  ci=qt(0.975,N-1)*se)

    ## # A tibble: 12 x 8
    ## # Groups:   variable [?]
    ##    variable     Species        N   moy   med    sd     se     ci
    ##    <chr>        <fct>      <int> <dbl> <dbl> <dbl>  <dbl>  <dbl>
    ##  1 Petal.Length setosa        50 1.46   1.5  0.174 0.0246 0.0494
    ##  2 Petal.Length versicolor    50 4.26   4.35 0.470 0.0665 0.134 
    ##  3 Petal.Length virginica     50 5.55   5.55 0.552 0.0780 0.157 
    ##  4 Petal.Width  setosa        50 0.246  0.2  0.105 0.0149 0.0300
    ##  5 Petal.Width  versicolor    50 1.33   1.3  0.198 0.0280 0.0562
    ##  6 Petal.Width  virginica     50 2.03   2    0.275 0.0388 0.0781
    ##  7 Sepal.Length setosa        50 5.01   5    0.352 0.0498 0.100 
    ##  8 Sepal.Length versicolor    50 5.94   5.9  0.516 0.0730 0.147 
    ##  9 Sepal.Length virginica     50 6.59   6.5  0.636 0.0899 0.181 
    ## 10 Sepal.Width  setosa        50 3.43   3.4  0.379 0.0536 0.108 
    ## 11 Sepal.Width  versicolor    50 2.77   2.8  0.314 0.0444 0.0892
    ## 12 Sepal.Width  virginica     50 2.97   3    0.322 0.0456 0.0917
    Et si on souhaite changer l’ordre d’apparition des variables Petal.Length, Petal Width, Sepal.Length et Sepal.Width, il faut :
  1. transformer la variable “variable” en facteur (car elle est considérée comme une chaîne de caractère)
  2. modifier les levels de la variable “variable”.
 
# permet de voir que la variable "variable" est une chaîne de caractères
str(iris_long)

## 'data.frame': 600 obs. of 3 variables:
## $ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
## $ variable: chr "Sepal.Length" "Sepal.Length" "Sepal.Length" "Sepal.Length" ...
## $ value : num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...

# transformation en facteur
iris_long$variable <- as.factor(iris_long$variable)

# ordre des modalités par défaut
levels(iris_long$variable)

## [1] "Petal.Length" "Petal.Width" "Sepal.Length" "Sepal.Width"

#modification de l'ordre
iris_long$variable <- factor(iris_long$variable, levels=c("Sepal.Length","Sepal.Width","Petal.Width","Petal.Length"))

# verification
levels(iris_long$variable)

## [1] "Sepal.Length" "Sepal.Width" "Petal.Width" "Petal.Length"

iris_long %>%
   group_by(variable) %>%
   summarise(N=n(),
   moy=mean(value, na.rm=TRUE),
   med=median(value, na.rm=TRUE),
   sd=sd(value, na.rm=TRUE),
   se=sd/sqrt(N),
   ci=qt(0.975,N-1)*se)


    ## # A tibble: 4 x 7
    ##   variable         N   moy   med    sd     se     ci
    ##   <fct>        <int> <dbl> <dbl> <dbl>  <dbl>  <dbl>
    ## 1 Sepal.Length   150  5.84  5.8  0.828 0.0676 0.134 
    ## 2 Sepal.Width    150  3.06  3    0.436 0.0356 0.0703
    ## 3 Petal.Width    150  1.20  1.3  0.762 0.0622 0.123 
    ## 4 Petal.Length   150  3.76  4.35 1.77  0.144  0.285
 

Dans la sortie, c’est à présent la variable Sepal.Length qui est en première ligne.

 

5. Quelques exemples pour le faceting avec ggplot2

De la même façon, le changement des levels , va entraîner une modification de l’ordre des sous plots dans le faceting. Par exemle, à présent c’est la variable Sepal Length qui est représentée dans le premier sous plot, puis la variable Sepal.Width, etc..

  ggplot(iris_long, aes(value)) +
        geom_density(fill="blue")+
        facet_wrap(~variable)
    faceting avec ggplot2

Et comme précédemment, si vous souhaitez obtenir la densité de ces quatre variables, mais en distinguant chacune des espèces, vous pouvez le faire en rajoutant la variable Species dans les fonctions facet_wrap() ou facet_grid() :

 ggplot(iris_long, aes(value)) +
        geom_density(fill="blue")+
        facet_wrap(Species~variable)
    faceting avec ggplot2 et R  
ggplot(iris_long, aes(value)) +
    geom_density(fill="blue")+
    facet_grid(Species~variable)
    faceting avec un format long

6 Repasser du format long au format wide

Pour cela, il est nécessaire d’utiliser la fonction inverse de la fonction gather(), qui est la fonction spread() (toujours dans le package tidyr). Cette fonction nécessite que le format long dispose d’un identifiant unique pour chaque ligne. Il est donc nécessaire de l’ajouter avant si celui-ci n’est pas présent. Dans l’exemple ci dessous, cela correspond à la variable “id”:

 my_iris

    ##   id Sepal.Length Sepal.Width Petal.Length Petal.Width
    ## 1  1          5.1         3.5          1.4         0.2
    ## 2  2          4.9         3.0          1.4         0.2
    ## 3  3          4.7         3.2          1.3         0.2
    ## 4  4          4.6         3.1          1.5         0.2
    ## 5  5          5.0         3.6          1.4         0.2

my_iris_long <- my_iris %>%
   gather(var, val, -id)

my_iris_long
   id          var val
1   1 Sepal.Length 5.1
2   2 Sepal.Length 4.9
3   3 Sepal.Length 4.7
4   4 Sepal.Length 4.6
5   5 Sepal.Length 5.0
6   1  Sepal.Width 3.5
7   2  Sepal.Width 3.0
8   3  Sepal.Width 3.2
9   4  Sepal.Width 3.1
10  5  Sepal.Width 3.6
11  1 Petal.Length 1.4
12  2 Petal.Length 1.4
13  3 Petal.Length 1.3
14  4 Petal.Length 1.5
15  5 Petal.Length 1.4
16  1  Petal.Width 0.2
17  2  Petal.Width 0.2
18  3  Petal.Width 0.2
19  4  Petal.Width 0.2
20  5  Petal.Width 0.2

my_iris2 <- my_iris_long %>%
    spread(var,val)

my_iris2

    ##   id Petal.Length Petal.Width Sepal.Length Sepal.Width
    ## 1  1          1.4         0.2          5.1         3.5
    ## 2  2          1.4         0.2          4.9         3.0
    ## 3  3          1.3         0.2          4.7         3.2
    ## 4  4          1.5         0.2          4.6         3.1
    ## 5  5          1.4         0.2          5.0         3.6
 

Conclusion :

J’espère que cet article vous permettra de vous approprier cette astuce consistant à passer d’un format wide à un format long pour faciliter vos analyses et vos visualisations. Si vous avez d’autres astuces, indiquez-les-moi en commentaire.

  Si cet article vous a plu, ou vous a été utile, et si vous le souhaitez, vous pouvez soutenir ce blog en faisant un don sur sa page Tipeee 🙏 👉 Cliquez ici pour soutenir le blog Statistiques et Logiciel R

Poursuivez votre lecture : 

5 réponses

  1. Bonjour Claire,

    Merci beaucoup pour ce post. C’est toujours un plaisir de recevoir le mail me prévenant d’un nouveau post. Je sais que je vais encore apprendre plein de chose.
    J’ai une question. Au tout début (1.1) comment passes-tu de la base “iris” à “my_iris”.
    J’ai pas bien compris le principe du format “tidy”.
    S’agit-il simplement de rajouter une colonne “id” ?
    J’avoue qu’il y a un point d’accroche que j’ai du mal à appréhender.

    Encore mille merci pour tous ces enseignement.

    1. Bonjour,
      j’ai créé le dataset my_iris comme cela :
      my_iris <- iris %>%
      mutate(id=1:n()) %>%
      select(id, everything(),-Species)

      my_iris <- my_iris[1:5,] C'était peut être maladroit, parce que le dataset iris est déjà en format tidy : une ligne par observation, et une colonne par variable. Donc non, pas besoin de rajouter une colonne id pour être en format tidy. J'espère que c'est plus compréhensible maintenant ! Bonne continuation

  2. Bonjour,
    merci beau pour cet article détaillé. Il semblerait que nous soyons dorénavant incités à utiliser les fonctions pivot_longer et pivot_wider, toujours du package tidyr pour les transformations de tableaux de données.

    Merci

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *