Filtrar filas que contienen una cadena determinada

188

Tengo que filtrar un marco de datos utilizando como criterio aquellas filas en las que está contenida la cadena RTB.

Estoy usando dplyr.

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

Sé que puedo utilizar la función filterde dplyrpero no exactamente cómo contarla para comprobar el contenido de una cadena.

En particular, quiero verificar el contenido en la columna TrackingPixel. Si la cadena contiene la etiqueta RTB, quiero eliminar la fila del resultado.

Gianluca
fuente
28
Nunca lo he usado dplyr, pero mirando la ayuda en ?dplyr::filtersugeriría algo como filter(df, !grepl("RTB",TrackingPixel))¿quizás?
thelatemail
2
Esto está realmente cerca de lo que quiero lograr. El único problema es mantener las cadenas que incluyen la etiqueta RTBy no mostrar las demás.
Gianluca
Acabo de poner una edición sigilosa, que ahora se invierte agregando el !frente de grepl- inténtalo de nuevo.
thelatemail
44
O use los argumentos inverty valuede grep. Las expresiones regulares hacen que trabajar con texto sea mil veces más fácil.
Rich Scriven
44
@thelatemail greplno funciona en postgres para mí, ¿es esto para MySQL?
Statwonk

Respuestas:

257

La respuesta a la pregunta ya fue publicada por @latemail en los comentarios anteriores. Puede usar expresiones regulares para el segundo argumento y los siguientes de filtereste modo:

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

Como no ha proporcionado los datos originales, agregaré un ejemplo de juguete utilizando el mtcarsconjunto de datos. Imagine que solo le interesan los automóviles producidos por 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

Si desea hacerlo al revés, es decir, excluyendo los automóviles Toyota y Mazda, el filtercomando se ve así:

dplyr::filter(mtcars, !grepl('Toyota|Mazda', type))
alex23lemm
fuente
¿Qué pasa si el nombre de la columna contiene un espacio? como seguimiento de píxeles.
MySchizoBuddy
3
asegúrese de estar utilizando la función de filtro del paquete dplyr, no el paquete de estadísticas
JHowIX
2
@MySchizoBuddy: si el nombre de la columna contiene espacios en blanco, puede seleccionar la variable con comillas inversas. Modificando el ejemplo anterior: mtcars$`my type` <- rownames(mtcars)y luegomtcars %>% filter(grepl('Toyota|Mazda', `my type`))
alex23lemm
13
tenga en cuenta que esto no funciona cuando el objeto es un tbl_sqlya greplque no se traduce a sql.
David LeBauer
la opción 1 es saber con seguridad que dplyr se cargó en último lugar. la opción 2 es su prefijo dplyr :: filter.
userJT
157

Solución

Es posible utilizar str_detectel stringrpaquete incluido en el tidyversepaquete. str_detectdevuelve Trueo Falsesi el vector especificado contiene alguna cadena específica. Es posible filtrar usando este valor booleano. Vea Introducción a stringr para obtener detalles sobre el stringrpaquete

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

Lo bueno de Stringr

Deberíamos usar en lugar stringr::str_detect()de base::grepl(). Esto se debe a que existen las siguientes razones.

  • Las funciones proporcionadas por el stringrpaquete comienzan con el prefijo str_, lo que hace que el código sea más fácil de leer.
  • El primer argumento de las funciones del stringrpaquete es siempre el data.frame (o valor), luego vienen los parámetros. (Gracias 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"

Punto de referencia

Los resultados de la prueba de referencia son los siguientes. Para grandes marcos de datos, str_detectes más rápido.

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
Keiku
fuente
1
¿Por qué stringr es una mejor opción que grep?
CameronNemo
2
@CameronNemo Las funciones proporcionadas por el stringrpaquete comienzan con el prefijo str_, que hace que el código sea más fácil de leer. En el código R moderno reciente, se recomienda usar stringr.
Keiku
3
Creo que esta es una preferencia muy personal y estoy de acuerdo con @CameronNemo que base Res tan bueno como stringr. Si nos proporcionara algunos "hechos concretos" como la evaluación comparativa, y no solo declarar "se recomienda" (¿Quién lo recomienda?), Esto sería muy apreciado. Gracias
Tjebo
2
Otra razón es la consistencia en el marco tidyverse: el primer argumento de una función es siempre el data.frame (o valor), luego vienen los parámetros.
Paolo
22

Esta respuesta es similar a otras, pero con preferencia stringr::str_detecty 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

Creado el 26-06-2018 por el paquete reprex (v0.2.0).

Ortiga
fuente
1
str_detectestá en el stringrpaquete
jsta
4

editar incluye la across()sintaxis más nueva

Aquí hay otra tidyversesolución, usando filter(across())o previamente filter_at. La ventaja es que puede extenderse fácilmente a más de una columna .

A continuación también una solución filter_allpara encontrar la cadena en cualquier columna, usando diamondscomo ejemplo, buscando la cadena "V"

library(tidyverse)

Cadena en una sola columna

# 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 sintaxis ahora reemplazada para la misma sería:

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

Cadena en todas las columnas:

# 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 sintaxis ahora reemplazada para la misma sería:

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

Traté de encontrar una alternativa cruzada para lo siguiente, pero no encontré inmediatamente una buena solución:

    #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

Actualización: Gracias al usuario Petr Kajzar en esta respuesta , aquí también un enfoque para lo anterior:

diamonds %>%
   filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)
Tjebo
fuente