Filtruj wiersze zawierające określony ciąg

188

Muszę filtrować ramkę danych, używając jako kryterium tych wierszy, w których znajduje się ciąg RTB.

Używam dplyr.

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

Wiem, że mogę korzystać z funkcji filterw dplyrale nie dokładnie, jak to powiedzieć, aby sprawdzić treść napisu.

W szczególności chcę sprawdzić zawartość w kolumnie TrackingPixel. Jeśli ciąg zawiera etykietę RTB, chcę usunąć wiersz z wyniku.

Gianluca
źródło
27
Nigdy nie korzystałem dplyr, ale patrząc na pomoc ?dplyr::filter, sugerowałbym coś w stylu filter(df, !grepl("RTB",TrackingPixel))może?
thelatemail
1
To właściwie jest blisko tego, co chcę osiągnąć. Jedynym problemem jest utrzymanie tych ciągów, które zawierają etykietę, RTBi nie pokazywanie pozostałych.
Gianluca,
Właśnie włączyłem ukrytą edycję, która jest teraz odwrócona przez dodanie !przed grepl- spróbuj ponownie.
thelatemail
4
Lub użyj argumentów inverti . Wyrażenia regularne sprawiają, że praca z tekstem jest tysiąc razy łatwiejsza. valuegrep
Rich Scriven
4
@ thelatemail greplnie działa na mnie dla postgres, czy to dla MySQL?
Statwonk,

Odpowiedzi:

255

Odpowiedź na pytanie została już opublikowana przez @latemail w powyższych komentarzach. Możesz użyć wyrażeń regularnych dla drugiego i kolejnych argumentów filtertakich jak to:

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

Ponieważ nie podałeś oryginalnych danych, dodam przykład zabawki, używając mtcarszestawu danych. Wyobraź sobie, że interesują Cię tylko samochody produkowane przez Mazdę lub Toyotę.

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

Jeśli chcesz to zrobić na odwrót, z wyłączeniem samochodów Toyota i Mazda, filterpolecenie wygląda następująco:

dplyr::filter(mtcars, !grepl('Toyota|Mazda', type))
alex23lemm
źródło
co jeśli nazwa kolumny zawiera spację. jak piksele śledzące.
MySchizoBuddy
3
upewnij się, że używasz funkcji filtrowania z pakietu dplyr, a nie pakietu statystyk
JHowIX
2
@MySchizoBuddy: Jeśli nazwa kolumny zawiera białe znaki, możesz wybrać zmienną za pomocą odwróconych znaków. Modyfikacja powyższego przykładu: mtcars$`my type` <- rownames(mtcars)a następniemtcars %>% filter(grepl('Toyota|Mazda', `my type`))
alex23lemm
13
zwróć uwagę, że to nie działa, gdy obiekt jest tbl_sqlas grepl, nie przekłada się na sql.
David LeBauer,
opcją 1 jest na pewno wiedzieć, że dplyr załadowano jako ostatni. opcja 2 to przedrostek dplyr :: filter.
userJT
157

Rozwiązanie

Jest możliwość korzystania str_detectz stringrpakietu zawartego w tidyversepakiecie. str_detectzwraca Truelub Falseczy określony wektor zawiera określony ciąg. Możliwe jest filtrowanie przy użyciu tej wartości logicznej. Zobacz Wprowadzenie do stringr, aby uzyskać szczegółowe informacje na temat stringrpakietu.

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

Dobre rzeczy w Stringr

Powinniśmy raczej użyć stringr::str_detect()niż base::grepl(). Wynika to z następujących przyczyn.

  • Funkcje dostarczane przez stringrpakiet rozpoczynają się od przedrostka str_, co ułatwia odczytanie kodu.
  • Pierwszym argumentem funkcji stringrpakietu jest zawsze data.frame (lub wartość), a następnie parametry. (Dziękujemy 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"

Reper

Wyniki testu porównawczego są następujące. W przypadku dużych ramek danych str_detectjest szybszy.

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
źródło
1
Dlaczego stringr jest lepszą opcją niż grep?
CameronNemo
2
@CameronNemo Funkcje dostarczane przez stringrpakiet zaczynają się od przedrostka str_, co ułatwia odczyt kodu. W najnowszym nowoczesnym kodzie R zaleca się stosowanie stringr.
Keiku
3
Myślę, że jest to bardzo osobista preferencja i zgadzam się z @CameronNemo, która base Rjest równie dobra stringr. Jeśli podasz nam pewne „twarde fakty”, takie jak analiza porównawcza, a nie tylko stwierdzenie „jest zalecane” (kto to poleca?), Byłoby to bardzo mile widziane. Dzięki
Tjebo
2
Innym powodem jest spójność frameworka tidyverse: pierwszym argumentem funkcji jest zawsze data.frame (lub wartość), a następnie parametry.
Paolo
22

Ta odpowiedź jest podobna do innych, ale używa preferowanych stringr::str_detecti 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

Utworzono 26.06.2018 przez pakiet reprezentx (v0.2.0).

Pokrzywa
źródło
1
str_detectjest w stringrpakiecie
jsta
3

edycja obejmowała nowszą across()składnię

Oto inne tidyverserozwiązanie, za pomocą filter(across())lub wcześniej filter_at. Zaletą jest to, że można łatwo rozszerzyć do więcej niż jednej kolumny .

Poniżej również rozwiązanie z filter_allw celu znalezienia ciągu w dowolnej kolumnie, używając diamondsjako przykładu szukania ciągu „V”

library(tidyverse)

Ciąg tylko w jednej kolumnie

# 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

Zastąpiona teraz składnia tego samego wyglądałaby następująco:

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

Ciąg we wszystkich kolumnach:

# 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

Zastąpiona teraz składnia tego samego wyglądałaby następująco:

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

Próbowałem znaleźć ogólną alternatywę dla następujących, ale nie od razu znalazłem dobre rozwiązanie:

    #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

Aktualizacja: Dzięki użytkownikowi Petrowi Kajzarowi w tej odpowiedzi , tutaj również podejście do powyższego:

diamonds %>%
   filter(rowSums(across(everything(), ~grepl("V", .x))) > 0)
Tjebo
źródło