logo

across, sa vie, son œuvre

Principe de dplyr::across

La fonction across est apparue dans la version 1.0 de {dplyr}, en juin 2020. Son but est de répéter une action sur plusieurs colonnes. On la retrouve principalement dans les fonctions mutate et summarise, mais elle peut aussi survenir avec une syntaxe légèrement adaptée dans group_by et arrange.

Usage courant dans mutate et summarise

La fonction across est une fonction-boucle au même titre que base::lapply ou que la famille des fonctions map du package {purrr}. C’est à dire qu’elle prend principalement deux arguments : le premier indique les éléments sur lesquels répéter l’action (ici les colonnes du data.frame), le second est le nom ou le code d’une fonction qui est l’action à répéter.

across(qui, quoi)

 

Nous allons compliquer les exemples petit à petit mais pour le moment, nous allons supposer que le “quoi” est une fonction qui ne prend d’autre argument que la colonne sur laquelle opérer : dans ce cas il suffit de nommer ladite fonction, sans parenthèses à sa suite.

library(dplyr)
mtcars %>% 
  mutate(across(c(am, vs, cyl),
                as.factor)) %>% 
  glimpse()

 

Rows: 32
Columns: 11
$ mpg  <dbl> 21.0, 21.0, 22.8, 21.4, 18.7, 18.1, 14.3, 24.4, 22.8, 19.2, 17.8,…
$ cyl  <fct> 6, 6, 4, 6, 8, 6, 8, 4, 4, 6, 6, 8, 8, 8, 8, 8, 8, 4, 4, 4, 4, 8,…
$ disp <dbl> 160.0, 160.0, 108.0, 258.0, 360.0, 225.0, 360.0, 146.7, 140.8, 16…
$ hp   <dbl> 110, 110, 93, 110, 175, 105, 245, 62, 95, 123, 123, 180, 180, 180…
$ drat <dbl> 3.90, 3.90, 3.85, 3.08, 3.15, 2.76, 3.21, 3.69, 3.92, 3.92, 3.92,…
$ wt   <dbl> 2.620, 2.875, 2.320, 3.215, 3.440, 3.460, 3.570, 3.190, 3.150, 3.…
$ qsec <dbl> 16.46, 17.02, 18.61, 19.44, 17.02, 20.22, 15.84, 20.00, 22.90, 18…
$ vs   <fct> 0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0,…
$ am   <fct> 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0,…
$ gear <dbl> 4, 4, 4, 3, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3,…
$ carb <dbl> 4, 4, 1, 1, 2, 1, 4, 2, 2, 4, 4, 3, 3, 3, 4, 4, 4, 1, 2, 1, 1, 2,…

Comme on le voit, les 3 colonnes indiquées dans le “qui” ont été converties en factors.

Si les données ne contiennent pas de valeurs manquantes on peut également utiliser across dans un summarise pour obtenir la même statistique sur toutes les colonnes.

mtcars %>% 
  summarise(across(everything(),
                   median))

 

   mpg cyl  disp  hp  drat    wt  qsec vs am gear carb
1 19.2   6 196.3 123 3.695 3.325 17.71  0  0    4    2

Ici, appliquer la fonction statistique sur toutes les colonnes est probablement assez brutal. On pourrait être plus délicats et se limiter aux colonnes numériques (en l’occurrence dans mtcars elles le sont toutes).

mtcars %>% 
  summarise(across(where(is.numeric),
                   median))

 

   mpg cyl  disp  hp  drat    wt  qsec vs am gear carb
1 19.2   6 196.3 123 3.695 3.325 17.71  0  0    4    2

Avec des arguments ou des calculs complexes comme fonction

La syntaxe précédente ne laisse la place à aucune option pour la fonction du “quoi”, autre que le nom de la colonne à traiter du “qui”. Si on a un besoin d’options, le plus rapide est de définir à la volée une fonction ad hoc qui inclura ces options. On parle dans ce cas de fonction anonyme ou de lambda fonction.

across(qui,
       ~ quoi(.x, options))

 

Dans cette syntaxe, la fonction du “quoi” peut être enrichie d’autant d’options que nécessaire. Le tilde (~) qui précède le nom de la fonction appelée est un raccourci pour la syntaxe R plus usuelle function(.x) {...}. L’argument du “qui” se nomme impérativement .x dans la syntaxe qui suit le tilde.

Par exemple on peut vouloir s’assurer de calculer des médianes qu’il y ait ou non des données manquantes dans le data.frame.

mtcars %>% 
  summarise(across(where(is.numeric),
                   ~ median(.x, na.rm=TRUE)))

 

   mpg cyl  disp  hp  drat    wt  qsec vs am gear carb
1 19.2   6 196.3 123 3.695 3.325 17.71  0  0    4    2

On peut aussi construire une fonction nouvelle après le tilde, mais il faut qu’elle reste assez simple pour que le code soit lisible. (Si votre fonction devient trop touffue, créez-la comme un objet séparé et appelez-la ensuite dans across.)

Ici on compte les données manquantes de chacune des colonnes du data.frame.

mtcars %>% 
  summarise(across(everything(),
                   ~ sum(is.na(.x))))

 

  mpg cyl disp hp drat wt qsec vs am gear carb
1   0   0    0  0    0  0    0  0  0    0    0

Avec plusieurs fonctions à appliquer

Le deuxième argument de across peut être une fonction, comme on l’a vu, ou une liste de fonctions. Il est conseillé de donner des noms à ces fonctions.

across(qui, 
       list(
         f1 = quoi1,
         f2 = quoi2
       ))
# ou avec des options, via des fonctions anonymes
across(qui, 
       list(
         f1 = ~ quoi1(.x, options1),
         f2 = ~ quoi2(.x, options2)
       ))

 

L’usage le plus courant de cette syntaxe est de calculer d’un coup différentes statistiques sur différentes colonnes sans devoir copier/coller le code pour créer toutes les combinaisons.

mtcars %>% 
  summarise(across(c(mpg, hp),
                   list(
                     "moyenne" = ~ mean(.x, na.rm=TRUE),
                     "mediane" = ~ median(.x, na.rm=TRUE),
                     "nb_obs"  = ~ sum( ! is.na(.x))
                   )))

 

  mpg_moyenne mpg_mediane mpg_nb_obs hp_moyenne hp_mediane hp_nb_obs
1    20.09062        19.2         32   146.6875        123        32

Avec des noms personnalisés pour les colonnes créées

On le voit dans ce dernier exemple, les colonnes créées sont automatiquement nommées selon le modèle qui_quoi en reprenant le nom de la colonne d’origine (“qui”) et le nom de la fonction ou de l’élément de liste qui lui est appliqué (“quoi”) avec un _ comme séparateur. C’est le choix par défaut mais il est personnalisable via l’option .names de across.

La valeur de l’option.names est un simple texte entre guillemets. Dans ce texte on décrit la manière de nommer les colonnes créées, avec deux mots-clés qui sont {.col} et {.fn} pour indiquer respectivement le “qui” et le “quoi”.

Vous aurez peut-être reconnu dans la logique de cette syntaxe celle de la fonction glue::glue à laquelle ce texte est automatiquement transmis. Comme dans un appel à glue on peut ajouter des appels à des fonctions à condition qu’ils se trouvent à l’intérieur de la paire d’accolades : par exemple on peut écrire {toupper(.col)} au lieu de {.col}.

mtcars %>% 
  summarise(across(c(mpg, hp),
                   list(
                     "MOY" = ~ mean(.x, na.rm=TRUE),
                     "N"   = ~ sum( ! is.na(.x))
                   ),
                   .names = "{.fn}_{toupper(.col)}"))

 

   MOY_MPG N_MPG   MOY_HP N_HP
1 20.09062    32 146.6875   32

Si nécessaire on peut également utiliser des informations externes comme un paramètre (un vecteur de longueur 1 disponible dans l’Environnement) à condition, là encore, de l’encadrer entre accolades.

annee <- 2026   # un paramètre qu'on veut intégrer aux noms
mtcars %>% 
  summarise(across(c(mpg, hp),
                   list(
                     "MOY" = ~ mean(.x, na.rm=TRUE),
                     "N"   = ~ sum( ! is.na(.x))
                   ),
                   .names = "{.fn}_{.col}_{annee}"))

 

  MOY_mpg_2026 N_mpg_2026 MOY_hp_2026 N_hp_2026
1     20.09062         32    146.6875        32

Avec différentes versions du “qui”

On a vu que le “qui”, le premier argument de across, pouvait être un vecteur de noms sans guillemets, un appel à la fonction everything() ou à la fonction where. Toutes les syntaxes qui fonctionnent dans dplyr::select sont en fait acceptées dans cet argument.

Parmi elles, il y a le cas particulier des paramètres, très utile quand on crée une fonction à l’intérieur de laquelle across est appelé. Si le paramètre est un vecteur texte de noms de colonnes (cette fois avec des guillemets) il est nécessaire d’appeler ce vecteur via la fonction all_of.

noms <- c("nom_var1", "nom_var2")

df %>%
  mutate/summarise(across(all_of(noms), ...))

 

En toute prudence on devrait même écrire all_of({{noms}}) pour bien indiquer la nature extérieure du vecteur noms : ce n’est pas une colonne du data.frame en entrée du pipeline mais un objet distinct de l’Environnement. Cependant dans ce contexte, aucune confusion n’est possible : c’est d’abord un objet de l’Environnement qui est recherché, et pas une colonne. Inutile d’alourdir la syntaxe avec ces doubles accolades, au contraire d’une fonction comme dplyr::filter par exemple où c’est très vivement conseillé.

variables <- c("mpg", "hp")

# sans all_of : fonctionne mais génère un warning
mtcars %>% 
  summarise(across(variables,
                   ~ max(.x, na.rm=TRUE)))

 

Warning: There was 1 warning in `summarise()`.
ℹ In argument: `across(variables, ~max(.x, na.rm = TRUE))`.
Caused by warning:
! Using an external vector in selections was deprecated in tidyselect 1.1.0.
ℹ Please use `all_of()` or `any_of()` instead.
  # Was:
  data %>% select(variables)

  # Now:
  data %>% select(all_of(variables))

See <https://tidyselect.r-lib.org/reference/faq-external-vector.html>.
   mpg  hp
1 33.9 335
# avec all_of : impeccable
mtcars %>% 
  summarise(across(all_of(variables),
                   ~ max(.x, na.rm=TRUE)))

 

   mpg  hp
1 33.9 335

L’intérêt est plus net quand on construit une fonction. Ici l’idée est de fournir un nom de data.frame sans guillemets, puis deux vecteurs (le second optionnel) de texte pour les noms des colonnes à résumer et les fonctions statistiques à leur appliquer.

statistiques <- function(data, 
                         var, 
                         stats=c("mean","max")){
  
  # construction de la partie "quoi" à partir du paramètre
  quoi <- lapply(stats, 
                 function(fn) 
                   as.formula(paste("~", fn,
                                    "(.x, na.rm=TRUE)")))
  names(quoi) <- stats
  
  res <- data %>% 
    summarise(across(all_of(var),
                     quoi))
  
  return(res)
}
statistiques(mtcars, "hp")

 

   hp_mean hp_max
1 146.6875    335
statistiques(mtcars, c("wt","mpg"))

 

  wt_mean wt_max mpg_mean mpg_max
1 3.21725  5.424 20.09062    33.9
statistiques(mtcars, c("wt","mpg"), stats=c("mean","sd"))

 

  wt_mean     wt_sd mpg_mean   mpg_sd
1 3.21725 0.9784574 20.09062 6.026948

Usage “détourné”

Dans arrange ou group_by

Dans une syntaxe basique, il n’y absolument pas besoin d’utiliser across à l’intérieur des fonctions arrange ou group_by puisqu’il suffit d’y énumérer les colonnes sur lesquelles on veut trier ou grouper. L’intérêt de across se limite à une syntaxe paramétrée, comme dans les exemples précédents, et particulièrement dans une fonction.

Dans ce cas, on utilise across avec un seul argument, le “qui”. Dans ce cas, il a le sens de “sur chacune de ces colonnes” ; sous-entendu, c’est sur chacune de ces colonnes qu’on trie / qu’on groupe.

arrange(across(all_of(noms)))
group_by(across(all_of(noms)))

 

variables <- c("mpg", "hp")

mtcars %>% 
  arrange(across(all_of(variables))) %>% 
  relocate(all_of(variables), .before=everything())

 

                     mpg  hp cyl  disp drat    wt  qsec vs am gear carb
Cadillac Fleetwood  10.4 205   8 472.0 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 10.4 215   8 460.0 3.00 5.424 17.82  0  0    3    4
Camaro Z28          13.3 245   8 350.0 3.73 3.840 15.41  0  0    3    4
Duster 360          14.3 245   8 360.0 3.21 3.570 15.84  0  0    3    4
Chrysler Imperial   14.7 230   8 440.0 3.23 5.345 17.42  0  0    3    4
Maserati Bora       15.0 335   8 301.0 3.54 3.570 14.60  0  1    5    8
AMC Javelin         15.2 150   8 304.0 3.15 3.435 17.30  0  0    3    2
Merc 450SLC         15.2 180   8 275.8 3.07 3.780 18.00  0  0    3    3
Dodge Challenger    15.5 150   8 318.0 2.76 3.520 16.87  0  0    3    2
Ford Pantera L      15.8 264   8 351.0 4.22 3.170 14.50  0  1    5    4
Merc 450SE          16.4 180   8 275.8 3.07 4.070 17.40  0  0    3    3
Merc 450SL          17.3 180   8 275.8 3.07 3.730 17.60  0  0    3    3
Merc 280C           17.8 123   6 167.6 3.92 3.440 18.90  1  0    4    4
Valiant             18.1 105   6 225.0 2.76 3.460 20.22  1  0    3    1
Hornet Sportabout   18.7 175   8 360.0 3.15 3.440 17.02  0  0    3    2
Merc 280            19.2 123   6 167.6 3.92 3.440 18.30  1  0    4    4
Pontiac Firebird    19.2 175   8 400.0 3.08 3.845 17.05  0  0    3    2
Ferrari Dino        19.7 175   6 145.0 3.62 2.770 15.50  0  1    5    6
Mazda RX4           21.0 110   6 160.0 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       21.0 110   6 160.0 3.90 2.875 17.02  0  1    4    4
Volvo 142E          21.4 109   4 121.0 4.11 2.780 18.60  1  1    4    2
Hornet 4 Drive      21.4 110   6 258.0 3.08 3.215 19.44  1  0    3    1
Toyota Corona       21.5  97   4 120.1 3.70 2.465 20.01  1  0    3    1
Datsun 710          22.8  93   4 108.0 3.85 2.320 18.61  1  1    4    1
Merc 230            22.8  95   4 140.8 3.92 3.150 22.90  1  0    4    2
Merc 240D           24.4  62   4 146.7 3.69 3.190 20.00  1  0    4    2
Porsche 914-2       26.0  91   4 120.3 4.43 2.140 16.70  0  1    5    2
Fiat X1-9           27.3  66   4  79.0 4.08 1.935 18.90  1  1    4    1
Honda Civic         30.4  52   4  75.7 4.93 1.615 18.52  1  1    4    2
Lotus Europa        30.4 113   4  95.1 3.77 1.513 16.90  1  1    5    2
Fiat 128            32.4  66   4  78.7 4.08 2.200 19.47  1  1    4    1
Toyota Corolla      33.9  65   4  71.1 4.22 1.835 19.90  1  1    4    1

On peut adapter la fonction construite précédemment pour y intégrer autant de variables de groupement qu’on souhaite, là encore sous forme de vecteur texte de noms entre guillemets.

statistiques <- function(data, 
                         var, 
                         groupes=NULL,
                         stats=c("mean","max")){
  
  # construction de la partie "quoi" à partir du paramètre
  quoi <- lapply(stats, 
                 function(fn) 
                   as.formula(paste("~", fn,
                                    "(.x, na.rm=TRUE)")))
  names(quoi) <- stats
  
  res <- data %>% 
    group_by(across(all_of(groupes))) %>% 
    summarise(across(all_of(var),
                     quoi)) %>% 
    ungroup()
  
  return(res)
}
statistiques(mtcars, "hp", groupes="cyl")

 

# A tibble: 3 × 3
    cyl hp_mean hp_max
  <dbl>   <dbl>  <dbl>
1     4    82.6    113
2     6   122.     175
3     8   209.     335
statistiques(mtcars, "hp") # sans groupes

 

# A tibble: 1 × 2
  hp_mean hp_max
    <dbl>  <dbl>
1    147.    335
statistiques(mtcars, 
             var=c("hp","wt"),
             groupes = c("cyl","am"),
             stats = "median")

 

`summarise()` has regrouped the output.
ℹ Summaries were computed grouped by cyl and am.
ℹ Output is grouped by cyl.
ℹ Use `summarise(.groups = "drop_last")` to silence this message.
ℹ Use `summarise(.by = c(cyl, am))` for per-operation grouping
  (`?dplyr::dplyr_by`) instead.
# A tibble: 6 × 4
    cyl    am hp_median wt_median
  <dbl> <dbl>     <dbl>     <dbl>
1     4     0      95        3.15
2     4     1      78.5      2.04
3     6     0     116.       3.44
4     6     1     110        2.77
5     8     0     180        3.81
6     8     1     300.       3.37

Dans rowSums et autres fonctions “horizontales”

Comme dans arrange ou group_by, l’utilisation de la syntaxe across(qui) sans autre argument peut également intervenir à l’intérieur de fonctions de calculs “horizontaux”. Par ce terme je veux désigner l’équivalent de fonctions de statistiques pour résumer un vecteur (une somme, une moyenne) mais qui s’appliquent à une série de valeurs dans une même ligne d’une matrice / d’un data.frame plutôt qu’au contenu d’une colonne. Par exemple l’équivalent horizontal de sum est rowSums, l’équivalent de mean est rowMeans.

mtcars %>% 
  mutate(total = rowSums(across(everything()))) %>% 
  relocate(total, .before = everything())

 

                      total  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
Mazda RX4           328.980 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
Mazda RX4 Wag       329.795 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
Datsun 710          259.580 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
Hornet 4 Drive      426.135 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
Hornet Sportabout   590.310 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
Valiant             385.540 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
Duster 360          656.920 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
Merc 240D           270.980 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
Merc 230            299.570 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
Merc 280            350.460 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
Merc 280C           349.660 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
Merc 450SE          510.740 16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
Merc 450SL          511.500 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
Merc 450SLC         509.850 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
Cadillac Fleetwood  728.560 10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
Lincoln Continental 726.644 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
Chrysler Imperial   725.695 14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
Fiat 128            213.850 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
Honda Civic         195.165 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
Toyota Corolla      206.955 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
Toyota Corona       273.775 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
Dodge Challenger    519.650 15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
AMC Javelin         506.085 15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
Camaro Z28          646.280 13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
Pontiac Firebird    631.175 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
Fiat X1-9           208.215 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
Porsche 914-2       272.570 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
Lotus Europa        273.683 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
Ford Pantera L      670.690 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
Ferrari Dino        379.590 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
Maserati Bora       694.710 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
Volvo 142E          288.890 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2

Si rowMeans et rowSums sont deux fonctions du package {base}, on n’y trouve pas des variantes pour toutes les fonctions de statistique descriptive verticales. Des packages comme {rje} ou {MatrixGenerics} proposent d’ajouter rowMins et rowMaxs mais on n’est jamais certain de trouver une version horizontale de la fonction statistique qui nous intéresse, même si {MatrixGenerics} est très généreuse de ce point de vue.

Les alternatives sont :

  • la fonction base::apply (qui s’applique normalement à une matrice, il importe donc que les types des colonnes renvoyées par across soient bien tous les mêmes) avec l’argument MARGIN=1 qui indique un calcul par ligne
    mtcars %>% 
      mutate(tot = apply(across(everything()), 
                         MARGIN=1, FUN=sum)) %>% 
      relocate(tot, .before = everything())

     

                            tot  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
    Mazda RX4           328.980 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
    Mazda RX4 Wag       329.795 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
    Datsun 710          259.580 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
    Hornet 4 Drive      426.135 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
    Hornet Sportabout   590.310 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
    Valiant             385.540 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
    Duster 360          656.920 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
    Merc 240D           270.980 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2
    Merc 230            299.570 22.8   4 140.8  95 3.92 3.150 22.90  1  0    4    2
    Merc 280            350.460 19.2   6 167.6 123 3.92 3.440 18.30  1  0    4    4
    Merc 280C           349.660 17.8   6 167.6 123 3.92 3.440 18.90  1  0    4    4
    Merc 450SE          510.740 16.4   8 275.8 180 3.07 4.070 17.40  0  0    3    3
    Merc 450SL          511.500 17.3   8 275.8 180 3.07 3.730 17.60  0  0    3    3
    Merc 450SLC         509.850 15.2   8 275.8 180 3.07 3.780 18.00  0  0    3    3
    Cadillac Fleetwood  728.560 10.4   8 472.0 205 2.93 5.250 17.98  0  0    3    4
    Lincoln Continental 726.644 10.4   8 460.0 215 3.00 5.424 17.82  0  0    3    4
    Chrysler Imperial   725.695 14.7   8 440.0 230 3.23 5.345 17.42  0  0    3    4
    Fiat 128            213.850 32.4   4  78.7  66 4.08 2.200 19.47  1  1    4    1
    Honda Civic         195.165 30.4   4  75.7  52 4.93 1.615 18.52  1  1    4    2
    Toyota Corolla      206.955 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
    Toyota Corona       273.775 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
    Dodge Challenger    519.650 15.5   8 318.0 150 2.76 3.520 16.87  0  0    3    2
    AMC Javelin         506.085 15.2   8 304.0 150 3.15 3.435 17.30  0  0    3    2
    Camaro Z28          646.280 13.3   8 350.0 245 3.73 3.840 15.41  0  0    3    4
    Pontiac Firebird    631.175 19.2   8 400.0 175 3.08 3.845 17.05  0  0    3    2
    Fiat X1-9           208.215 27.3   4  79.0  66 4.08 1.935 18.90  1  1    4    1
    Porsche 914-2       272.570 26.0   4 120.3  91 4.43 2.140 16.70  0  1    5    2
    Lotus Europa        273.683 30.4   4  95.1 113 3.77 1.513 16.90  1  1    5    2
    Ford Pantera L      670.690 15.8   8 351.0 264 4.22 3.170 14.50  0  1    5    4
    Ferrari Dino        379.590 19.7   6 145.0 175 3.62 2.770 15.50  0  1    5    6
    Maserati Bora       694.710 15.0   8 301.0 335 3.54 3.570 14.60  0  1    5    8
    Volvo 142E          288.890 21.4   4 121.0 109 4.11 2.780 18.60  1  1    4    2
  • la fonction dplyr::rowwise qui indique également des calculs par ligne jusqu’au prochain ungroup et permet dans ce cas d’utiliser la fonction “verticale” appliquée au across pour un effet horizontal.
    mtcars %>% 
      rowwise() %>% 
      mutate(tot = sum(across(everything()))) %>% 
      ungroup() %>% 
      relocate(tot, .before = everything())

     

    # A tibble: 32 × 12
         tot   mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb
       <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
     1  329.  21       6  160    110  3.9   2.62  16.5     0     1     4     4
     2  330.  21       6  160    110  3.9   2.88  17.0     0     1     4     4
     3  260.  22.8     4  108     93  3.85  2.32  18.6     1     1     4     1
     4  426.  21.4     6  258    110  3.08  3.22  19.4     1     0     3     1
     5  590.  18.7     8  360    175  3.15  3.44  17.0     0     0     3     2
     6  386.  18.1     6  225    105  2.76  3.46  20.2     1     0     3     1
     7  657.  14.3     8  360    245  3.21  3.57  15.8     0     0     3     4
     8  271.  24.4     4  147.    62  3.69  3.19  20       1     0     4     2
     9  300.  22.8     4  141.    95  3.92  3.15  22.9     1     0     4     2
    10  350.  19.2     6  168.   123  3.92  3.44  18.3     1     0     4     4
    # ℹ 22 more rows

Si les trois approchent donnent des résultats équivalents, les deux premières ont quasiment les mêmes temps d’exécution, tandis que rowwise est beaucoup plus lente (d’un facteur 10 !). Globalement le recours à rowwise doit être vu comme une solution de la dernière chance, très lente, et dans laquelle il ne faut jamais omettre le ungroup().

Remarques

  • La fonction across permet de se dispenser des “vieilles” fonctions comme mutate_if, mutate_at et mutate_all, et les mêmes variantes pour summarise. Ces fonctions existent toujours dans {dplyr} (en tout cas dans la version 1.2.1 utilisée pour rédiger cet article) mais sont en voie d’abandon (deprecated) et sont donc susceptibles de disparaître d’une version future. Si vous avez des *_if/at/all dans votre code, remplacez-les par un recours à across.
  • Dans les fonctions du “quoi” on peut réaliser des calculs plus sophistiqués que les opérations sur la colonne en cours. Les fonctions cur_column et cur_group permettent des calculs complexes impliquant plusieurs colonnes mais sont plus difficiles à manier. On dépasse ici le cadre de cette petite explication.
  • Vous vous demandez comment utiliser across dans votre contexte particulier ? Nous pouvons organiser une formation : olivier.decourt@od-datamining.com
sessionInfo()

 

R version 4.5.2 (2025-10-31 ucrt)
Platform: x86_64-w64-mingw32/x64
Running under: Windows 10 x64 (build 19045)

Matrix products: default
  LAPACK version 3.12.1

locale:
[1] LC_COLLATE=French_France.utf8  LC_CTYPE=French_France.utf8   
[3] LC_MONETARY=French_France.utf8 LC_NUMERIC=C                  
[5] LC_TIME=French_France.utf8    

time zone: Europe/Paris
tzcode source: internal

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] dplyr_1.2.1

loaded via a namespace (and not attached):
 [1] digest_0.6.37     R6_2.6.1          fastmap_1.2.0     tidyselect_1.2.1 
 [5] xfun_0.59         magrittr_2.0.3    glue_1.8.1        tibble_3.3.0     
 [9] knitr_1.51        pkgconfig_2.0.3   htmltools_0.5.8.1 generics_0.1.4   
[13] rmarkdown_2.29    lifecycle_1.0.5   cli_3.6.5         vctrs_0.7.3      
[17] withr_3.0.3       compiler_4.5.2    rstudioapi_0.19.0 tools_4.5.2      
[21] pillar_1.11.0     evaluate_1.0.5    yaml_2.3.12       otel_0.2.0       
[25] rlang_1.2.0       jsonlite_2.0.0    htmlwidgets_1.6.4

2 found this helpful