On nous demande souvent comment réaliser des opérations par lignes dans un data.frame
(ou un tibble
) la réponse est, comme souvent, “ca dépend” 🙂
Voyons ensemble quelques cas de figure qui devraient correspondre à vos besoins.
library(tidyverse)
Fabriquons un jeu de données d’exemple :
base <- tibble::tibble(
a = 1:10,
b = 1:10,
c = 21:30
) %>% head()
base
## # A tibble: 6 × 3
## a b c
## <int> <int> <int>
## 1 1 1 21
## 2 2 2 22
## 3 3 3 23
## 4 4 4 24
## 5 5 5 25
## 6 6 6 26
Imaginons que l’on souhaite ajouter une colonne new
dont la valeur va dépendre du contenu, par ligne, des colonnes a
, b
et c
de notre base
d’exemple
Comme ceci :
# A tibble: 6 x 4
a b c new
<int> <int> <int> <chr>
1 1 1 21 a vaut 1
2 2 2 22 autre cas
3 3 3 23 autre cas
4 4 4 24 autre cas
5 5 5 25 c vaut 25
6 6 6 26 autre cas
Avec case_when()
Table des matières
base %>%
mutate(
new = case_when(
a == 1 ~ "a vaut 1",
c == 25 ~ "c vaut 25",
TRUE ~ "autre cas"
)
)
## # A tibble: 6 × 4
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 autre cas
## 3 3 3 23 autre cas
## 4 4 4 24 autre cas
## 5 5 5 25 c vaut 25
## 6 6 6 26 autre cas
case_when()
c’est bien, c’est bien plus lisible que des ifelse()
imbriqués, mais ça peut vite se complexifier.
Fabriquons alors une fonction qui, en fonction des valeurs de a
, b
, c
, nous retourne la valeur attendue.
En fonction du cas de figure (et de vos compétences) vous aurez parfois une fonction vectorisée et parfois une fonction non vectorisée. Il faut toujours privilégier la création de fonction vectorisée, mais ce n’est pas toujours possible.
Une fonction vectorisée est une fonction que l’on peut directement appliquer à un ensemble de vecteurs et qui retourne un vecteur de réponse.
Exemple de fonction vectorisée qui reprend les opérations du case_when()
précédent :
fonction_vectorise <- function(a, b, c, ...){
ifelse(a == 1 , "a vaut 1",
ifelse(c == 25 , "c vaut 25",
"autre cas"
))
}
fonction_vectorise(a = 1, c = 25, b = "R")
## [1] "a vaut 1"
fonction_vectorise(a = c(1, 1, 3), c = 27:25, b = "R")
## [1] "a vaut 1" "a vaut 1" "c vaut 25"
Voici la “même” fonction, mais non vectorisée :
fonction_non_vectorise <- function(a, b, c, ...){
if ( a == 1 ) { return("a vaut 1") }
if ( c == 25 ) { return("c vaut 25") }
return("autre")
}
fonction_non_vectorise(a = 1, c = 25, b = "R")
## [1] "a vaut 1"
fonction_non_vectorise(a = c(1, 1, 3), c = 27:25, b = "R") # ne fonctionne pas
## Warning in if (a == 1) {: la condition a une longueur > 1 et seul le
## premier élément est utilisé
## [1] "a vaut 1"
Avec une fonction vectorisée
C’est le cas le plus simple, le plus rapide aussi.
Vous pouvez l’utiliser en l’état dans un mutate()
:
base %>%
mutate(
new = fonction_vectorise(a = a, b = b, c = c)
)
## # A tibble: 6 × 4
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 autre cas
## 3 3 3 23 autre cas
## 4 4 4 24 autre cas
## 5 5 5 25 c vaut 25
## 6 6 6 26 autre cas
Avec une fonction NON vectorisée
Le résultat retourné par un mutate()
n’est pas correct (la première valeur retournée est répétée…)
base %>%
mutate(
new = fonction_non_vectorise(a = a, b = b, c = c)
)
## Warning in if (a == 1) {: la condition a une longueur > 1 et seul le
## premier élément est utilisé
## # A tibble: 6 × 4
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 a vaut 1
## 3 3 3 23 a vaut 1
## 4 4 4 24 a vaut 1
## 5 5 5 25 a vaut 1
## 6 6 6 26 a vaut 1
Changeons donc de stratégie.
Avec rowwise()
rowwise()
est redevenue d’actualité dans le monde de {dplyr} et elle est spécifiquement conçue pour ce cas de figure :
base %>%
rowwise() %>%
mutate(
new = fonction_non_vectorise(a = a, b = b, c = c)
)
## # A tibble: 6 × 4
## # Rowwise:
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 autre
## 3 3 3 23 autre
## 4 4 4 24 autre
## 5 5 5 25 c vaut 25
## 6 6 6 26 autre
Avec pmap()
base %>%
mutate(
new = pmap_chr(list(a = a, b = b, c = c), fonction_non_vectorise)
)
## # A tibble: 6 × 4
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 autre
## 3 3 3 23 autre
## 4 4 4 24 autre
## 5 5 5 25 c vaut 25
## 6 6 6 26 autre
Bonus avec Vectorize()
La fonction Vectorize()
permet de vectoriser une fonction…
C’est un peu de la triche, mais ça peut dépanner 🙂
base %>%
mutate(
new = Vectorize(fonction_non_vectorise)(a = a, b = b, c = c)
)
## # A tibble: 6 × 4
## a b c new
## <int> <int> <int> <chr>
## 1 1 1 21 a vaut 1
## 2 2 2 22 autre
## 3 3 3 23 autre
## 4 4 4 24 autre
## 5 5 5 25 c vaut 25
## 6 6 6 26 autre
A vous les opérations en lignes !
Expérimentez et dîtes-nous quelles sont vos pratiques !
Pour aller plus loin: https://dplyr.tidyverse.org/articles/rowwise.html