web-dev-qa-db-fra.com

Filtrage efficace via plusieurs colonnes par groupe

Supposons un jeu de données contenant plusieurs lignes par identifiant et plusieurs colonnes contenant des codes stockés comme chaînes:

df <- data.frame(id = rep(1:3, each = 2),
                 var1 = c("X1", "Y1", "Y2", "Y3", "Z1", "Z2"),
                 var2 = c("Y1", "X2", "Y2", "Y3", "Z1", "Z2"),
                 var3 = c("Y1", "Y2", "X1", "Y3", "Z1", "Z2"),
                 stringsAsFactors = FALSE)

  id var1 var2 var3
1  1   X1   Y1   Y1
2  1   Y1   X2   Y2
3  2   Y2   Y2   X1
4  2   Y3   Y3   Y3
5  3   Z1   Z1   Z1
6  3   Z2   Z2   Z2

Supposons maintenant que je veux filtrer tous les ID qui ont un code spécifique (ici X) dans l'une des colonnes correspondantes. Avec dplyr et purrr, je pourrais faire:

df %>%
 group_by(id) %>%
 filter(all(reduce(.x = across(var1:var3, ~ !grepl("^X", .)), .f = `&`)))

     id var1  var2  var3 
  <int> <chr> <chr> <chr>
1     3 Z1    Z1    Z1   
2     3 Z2    Z2    Z2 

Cela fonctionne bien, c'est compact et il est facile de comprendre, cependant, il est assez inefficace avec de grands ensembles de données (des millions d'identifiants et des dizaines de millions d'observations). Je souhaiterais recevoir des idées de code de calcul plus efficace, en utilisant n'importe quelle bibliothèque.

15
tmfmnk

Une autre solution de base r, que je n'ai pas vue mentionnée encore. Utilise Modulo par le nombre de lignes pour renvoyer rapidement les lignes pour supprimer:

df[!(df$id %in% df$id[(which(df=="X1" | df=="X2") %% nrow(df))]),]
id var1 var2 var3
5  3   Z1   Z1   Z1
6  3   Z2   Z2   Z2

Il est rapide, mesuré en microsecondes:

library(microbenchmark)
microbenchmark(df[!(df$id %in% df$id[(which(df=="X1" | df=="X2") %% nrow(df))]),])
Unit: microseconds
min       lq     mean   median       uq     max
136.601 140.8505 165.7009 145.4515 172.9005 328.801 
0
user16051136

Cette approche combine toutes les colonnes de pâte, puis comptez sur StringR pour produire un vecteur avec tous les identifiants où 'X' est présent.

library(tidyverse)
library(stringr)

df <- data.frame(id = rep(1:3, each = 2),
                 var1 = c("X1", "Y1", "Y2", "Y3", "Z1", "Z2"),
                 var2 = c("Y1", "X2", "Y2", "Y3", "Z1", "Z2"),
                 var3 = c("Y1", "Y2", "X1", "Y3", "Z1", "Z2"),
                 stringsAsFactors = FALSE)


system.time({df %>%
        group_by(id) %>%
        filter(all(reduce(.x = across(var1:var3, ~ !grepl("^X", .)), .f = `&`)))})
#>    user  system elapsed 
#>   0.022   0.001   0.023


#answer 
system.time({
criteria <- as.numeric(paste0(df$var1, df$var2, df$var3, '-', df$id) %>%
                {str_sub(.[str_detect(., 'X')], start = -1)} |>
                unique())

df_filtered <- filter(df, !id %in% criteria)
})
#>    user  system elapsed 
#>   0.002   0.000   0.001

df_filtered
#>   id var1 var2 var3
#> 1  3   Z1   Z1   Z1
#> 2  3   Z2   Z2   Z2

Créé le 2021-06-15 par le package Reprex (v2.0.0)

0
jpdugo17