Filtra le righe che contengono una determinata stringa


188

Devo filtrare un frame di dati usando come criterio quelle righe in cui è contenuta la stringa RTB.

Sto usando dplyr.

d.del <- df %.%
  group_by(TrackingPixel) %.%
  summarise(MonthDelivery = as.integer(sum(Revenue))) %.%
  arrange(desc(MonthDelivery))

So che posso utilizzare la funzione filterin dplyr, ma io non esattamente come dire che per verificare il contenuto di una stringa.

In particolare, voglio controllare il contenuto nella colonna TrackingPixel. Se la stringa contiene l'etichetta, RTBdesidero rimuovere la riga dal risultato.


28
Non ho mai usato dplyr, ma guardando l'aiuto in ?dplyr::filtersuggerirei qualcosa come filter(df, !grepl("RTB",TrackingPixel))forse?
thelatemail

2
Questo è in realtà vicino a ciò che voglio ottenere. L'unico problema è che mantiene quelle stringhe che includono l'etichetta RTBe non mostrano le altre.
Gianluca,

Ho appena inserito una modifica invisibile, che è ora invertita aggiungendo il !davanti a grepl- riprovare.
thelatemail

4
Oppure usa gli argomenti inverte valuedi grep. Le espressioni regolari rendono il lavoro con il testo mille volte più semplice.
Rich Scriven,

4
@thelatemail greplnon funziona su Postgres per me, è questo per MySQL?
Statwonk,

Risposte:


256

La risposta alla domanda era già stata pubblicata da @latemail nei commenti sopra. Puoi usare espressioni regolari per il secondo e successivi argomenti filtercome questo:

dplyr::filter(df, !grepl("RTB",TrackingPixel))

Dato che non hai fornito i dati originali, aggiungerò un esempio di giocattolo usando il mtcarsset di dati. Immagina di essere interessato solo alle auto prodotte da Mazda o Toyota.

mtcars$type <- rownames(mtcars)
dplyr::filter(mtcars, grepl('Toyota|Mazda', type))

   mpg cyl  disp  hp drat    wt  qsec vs am gear carb           type
1 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4      Mazda RX4
2 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4  Mazda RX4 Wag
3 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1 Toyota Corolla
4 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1  Toyota Corona

Se desideri farlo al contrario, vale a dire escludendo le auto Toyota e Mazda, il filtercomando è simile al seguente:

dplyr::filter(mtcars, !grepl('Toyota|Mazda', type))

cosa succede se il nome della colonna contiene uno spazio. come i pixel di tracciamento.
MySchizoBuddy,

3
assicurati di utilizzare la funzione filtro dal pacchetto dplyr, non dal pacchetto stats
JHowIX,

2
@MySchizoBuddy: se il nome della colonna contiene spazi bianchi, è possibile selezionare la variabile usando i backtick. Modifica l'esempio sopra: mtcars$`my type` <- rownames(mtcars)e poimtcars %>% filter(grepl('Toyota|Mazda', `my type`))
alex23lemm

13
si noti che questo non funziona quando l'oggetto è un tbl_sqlas greplnon si traduce in sql.
David LeBauer,

l'opzione 1 è sapere per certo che dplyr è stato caricato per ultimo. l'opzione 2 è il prefisso dplyr :: filter.
userJT

157

Soluzione

È possibile utilizzare str_detectil stringrpacchetto incluso nel tidyversepacchetto. str_detectrestituisce Trueo Falsese il vettore specificato contiene una stringa specifica. È possibile filtrare utilizzando questo valore booleano. Vedi Introduzione a stringr per dettagli sul stringrpacchetto.

library(tidyverse)
# ─ Attaching packages ──────────────────── tidyverse 1.2.1 ─
# ✔ ggplot2 2.2.1     ✔ purrr   0.2.4
# ✔ tibble  1.4.2     ✔ dplyr   0.7.4
# ✔ tidyr   0.7.2     ✔ stringr 1.2.0
# ✔ readr   1.1.1     ✔ forcats 0.3.0
# ─ Conflicts ───────────────────── tidyverse_conflicts() ─
# ✖ dplyr::filter() masks stats::filter()
# ✖ dplyr::lag()    masks stats::lag()

mtcars$type <- rownames(mtcars)
mtcars %>%
  filter(str_detect(type, 'Toyota|Mazda'))
# mpg cyl  disp  hp drat    wt  qsec vs am gear carb           type
# 1 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4      Mazda RX4
# 2 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4  Mazda RX4 Wag
# 3 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1 Toyota Corolla
# 4 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1  Toyota Corona

Le cose buone di Stringr

Dovremmo usare piuttosto stringr::str_detect()che base::grepl(). Questo perché ci sono i seguenti motivi.

  • Le funzioni fornite dal stringrpacchetto iniziano con il prefisso str_, che semplifica la lettura del codice.
  • Il primo argomento delle funzioni del stringrpacchetto è sempre data.frame (o valore), quindi arrivano i parametri. (Grazie Paolo)
object <- "stringr"
# The functions with the same prefix `str_`.
# The first argument is an object.
stringr::str_count(object) # -> 7
stringr::str_sub(object, 1, 3) # -> "str"
stringr::str_detect(object, "str") # -> TRUE
stringr::str_replace(object, "str", "") # -> "ingr"
# The function names without common points.
# The position of the argument of the object also does not match.
base::nchar(object) # -> 7
base::substr(object, 1, 3) # -> "str"
base::grepl("str", object) # -> TRUE
base::sub("str", "", object) # -> "ingr"

Prova delle prestazioni

I risultati del test di riferimento sono i seguenti. Per frame di dati di grandi dimensioni, str_detectè più veloce.

library(rbenchmark)
library(tidyverse)

# The data. Data expo 09. ASA Statistics Computing and Graphics 
# http://stat-computing.org/dataexpo/2009/the-data.html
df <- read_csv("Downloads/2008.csv")
print(dim(df))
# [1] 7009728      29

benchmark(
  "str_detect" = {df %>% filter(str_detect(Dest, 'MCO|BWI'))},
  "grepl" = {df %>% filter(grepl('MCO|BWI', Dest))},
  replications = 10,
  columns = c("test", "replications", "elapsed", "relative", "user.self", "sys.self"))
# test replications elapsed relative user.self sys.self
# 2      grepl           10  16.480    1.513    16.195    0.248
# 1 str_detect           10  10.891    1.000     9.594    1.281

1
Perché stringr è un'opzione migliore di grep?
CameronNemo,

2
@CameronNemo Le funzioni fornite dal stringrpacchetto iniziano con il prefisso str_, che semplifica la lettura del codice. Nel recente codice R moderno, si consiglia di utilizzare stringr.
Keiku,

3
Penso che questa sia una preferenza molto personale e sono d'accordo con @CameronNemo che base Rè buono come stringr. Se ci fornissi alcuni 'fatti concreti' come il benchmarking, e non solo affermando che "è raccomandato" (Chi lo consiglia?), Questo sarebbe molto apprezzato. Grazie
Tjebo

2
Un altro motivo è la coerenza nel framework tidyverse: il primo argomento di una funzione è sempre data.frame (o valore), quindi arrivano i parametri.
Paolo

22

Questa risposta è simile ad altre, ma usando le stringr::str_detectopzioni preferite e dplyr rownames_to_column.

library(tidyverse)

mtcars %>% 
  rownames_to_column("type") %>% 
  filter(stringr::str_detect(type, 'Toyota|Mazda') )

#>             type  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> 1      Mazda RX4 21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> 2  Mazda RX4 Wag 21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> 3 Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
#> 4  Toyota Corona 21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1

Creato il 26/06/2018 dal pacchetto reprex (v0.2.0).


1
str_detectè nel stringrpacchetto
jsta

3

modifica includeva la across()sintassi più recente

Ecco un'altra tidyversesoluzione, utilizzando filter(across())o in precedenza filter_at. Il vantaggio è che puoi estenderti facilmente a più di una colonna .

Di seguito anche una soluzione con filter_allper trovare la stringa in qualsiasi colonna, usando diamondscome esempio, cercando la stringa "V"

library(tidyverse)

Stringa in una sola colonna

# for only one column... extendable to more than one creating a column list in `across` or `vars`!
mtcars %>% 
  rownames_to_column("type") %>% 
  filter(across(type, ~ !grepl('Toyota|Mazda', .))) %>%
  head()
#>                type  mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> 1        Datsun 710 22.8   4 108.0  93 3.85 2.320 18.61  1  1    4    1
#> 2    Hornet 4 Drive 21.4   6 258.0 110 3.08 3.215 19.44  1  0    3    1
#> 3 Hornet Sportabout 18.7   8 360.0 175 3.15 3.440 17.02  0  0    3    2
#> 4           Valiant 18.1   6 225.0 105 2.76 3.460 20.22  1  0    3    1
#> 5        Duster 360 14.3   8 360.0 245 3.21 3.570 15.84  0  0    3    4
#> 6         Merc 240D 24.4   4 146.7  62 3.69 3.190 20.00  1  0    4    2

La sintassi ora sostituita per lo stesso sarebbe:

mtcars %>% 
  rownames_to_column("type") %>% 
  filter_at(.vars= vars(type), all_vars(!grepl('Toyota|Mazda',.))) 

Stringa in tutte le colonne:

# remove all rows where any column contains 'V'
diamonds %>%
  filter(across(everything(), ~ !grepl('V', .))) %>%
  head
#> # A tibble: 6 x 10
#>   carat cut     color clarity depth table price     x     y     z
#>   <dbl> <ord>   <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
#> 1  0.23 Ideal   E     SI2      61.5    55   326  3.95  3.98  2.43
#> 2  0.21 Premium E     SI1      59.8    61   326  3.89  3.84  2.31
#> 3  0.31 Good    J     SI2      63.3    58   335  4.34  4.35  2.75
#> 4  0.3  Good    J     SI1      64      55   339  4.25  4.28  2.73
#> 5  0.22 Premium F     SI1      60.4    61   342  3.88  3.84  2.33
#> 6  0.31 Ideal   J     SI2      62.2    54   344  4.35  4.37  2.71

La sintassi ora sostituita per lo stesso sarebbe:

diamonds %>% 
  filter_all(all_vars(!grepl('V', .))) %>%
  head

Ho provato a trovare un'alternativa trasversale per quanto segue, ma non ho trovato immediatamente una buona soluzione:

    #get all rows where any column contains 'V'
    diamonds %>%
    filter_all(any_vars(grepl('V',.))) %>%
      head
    #> # A tibble: 6 x 10
    #>   carat cut       color clarity depth table price     x     y     z
    #>   <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
    #> 1 0.23  Good      E     VS1      56.9    65   327  4.05  4.07  2.31
    #> 2 0.290 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
    #> 3 0.24  Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
    #> 4 0.24  Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
    #> 5 0.26  Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
    #> 6 0.22  Fair      E     VS2      65.1    61   337  3.87  3.78  2.49

Aggiornamento: grazie all'utente Petr Kajzar in questa risposta , ecco anche un approccio per quanto sopra:

diamonds %>%
   filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.