过滤包含特定字符串的行

308

我需要使用 dplyr 来筛选数据框中包含字符串 RTB 的行。

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

我知道可以在dplyr中使用函数filter,但我不确定如何让它检查字符串的内容。

特别是我想检查列TrackingPixel中的内容。如果字符串包含标签RTB,我想从结果中删除该行。


42
我从未使用过dplyr,但查看了?dplyr::filter的帮助文档后,我建议尝试使用类似以下代码:filter(df, !grepl("RTB", TrackingPixel)) - thelatemail
2
这实际上接近我想要实现的内容。唯一的问题是保留包含标签“RTB”的字符串并不显示其他字符串。 - Gianluca
我刚刚进行了一个隐蔽的编辑,现在通过在grepl前面添加!来撤销它 - 再试一次。 - thelatemail
4
或者使用 grepinvertvalue 参数。正则表达式使得处理文本变得轻松许多。 - Rich Scriven
4
grepl在我的Postgres上无法正常工作,这是针对MySQL的吗? - Statwonk
5个回答

369

@latemail在上面的评论中已经发布了问题的答案。您可以像这样使用正则表达式来为filter 的第二个和后续参数进行筛选:

问题的答案已经被@latemail在上述评论中发布。你可以像这样使用正则表达式来对 filter 中的第二个及后续参数进行筛选:

(Note: The two translations have slight differences in wording due to the ambiguity of the original text. Please choose the one that better suits your needs.)
dplyr::filter(df, !grepl("RTB",TrackingPixel))

由于您没有提供原始数据,我将使用mtcars数据集添加一个玩具示例。假设您只对马自达或丰田生产的汽车感兴趣。

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

如果你想要排除丰田和马自达汽车,filter 命令看起来是这样的:

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

如果列名包含空格,例如“Tracking Pixels”,该怎么办? - MySchizoBuddy
4
请确保您使用的是dplyr软件包中的filter函数,而不是stats软件包中的filter函数。 - JHowIX
2
@MySchizoBuddy:如果列名包含空格,您可以使用反引号选择变量。 修改上面的示例:mtcars$\my type` <- rownames(mtcars)然后mtcars %>% filter(grepl('Toyota|Mazda', `my type`))` - alex23lemm
13
请注意,当对象是“tbl_sql”时,此方法无效,因为“grepl”函数无法转换为SQL语言。 - David LeBauer
1
选项1是确保dplyr最后加载。选项2是在filter前加上dplyr::。 - userJT

248

解决方案

可以使用包含在tidyverse包中的stringr包的str_detect函数。 str_detect函数返回一个布尔值,指示指定的向量是否包含某个特定字符串。 可以使用此布尔值进行过滤。有关stringr包的详细信息,请参见Introduction to stringr

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

关于Stringr的好处

我们应该使用stringr::str_detect()而不是base::grepl()。原因如下:

  • stringr软件包提供的函数以前缀str_开头,使代码更易读。
  • stringr软件包函数的第一个参数始终是数据框(或值),其次是参数。(感谢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"

基准测试

基准测试结果如下。对于大型数据框,str_detect 更快。

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
为什么 stringr 比 grep 更好? - CameronNemo
3
stringr包提供的函数都以前缀str_开头,这使得代码更易于阅读。在现代的R代码中,建议使用stringr - Keiku
3
我认为这是非常个人化的偏好,我同意@CameronNemo的观点,即“base R”和“stringr”一样好。如果您能提供一些“硬性事实”,例如基准测试,而不仅仅是陈述“建议使用”(谁建议使用?),我们将不胜感激。谢谢。 - tjebo
5
另一个原因是tidyverse框架的一致性:函数的第一个参数总是数据框(或值),然后是参数。 - Paolo
1
刚刚偶然看到这篇帖子,我知道这是一个旧的讨论,但以防万一有其他人来到这里:人们可能更喜欢使用str_detect而不是grepl的原因之一是,如果存在缺失值,str_detect将返回NA,而grepl将返回FALSE,这可能会导致误解。 - Claudio
显示剩余3条评论

53

这个答案与其他答案相似,但使用了首选的stringr::str_detect和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

reprex package (v0.2.0) 创建于2018年6月26日。


20

这里是另一个使用 dplyr 的解决方案,可以使用 filter(if_all/if_any) 来轻松扩展至多个列。下面以 diamonds 作为例子,演示如何过滤包含字符串“V”的任意列的行。


编辑以反映 dplyr 语法的更改(>=dplyr vs. 1.0.10)。之前使用了 across (现在已弃用),甚至在此之前使用了 filter_allfilter_any(已被取代)。


删除任意列满足条件的行

library(dplyr)

## with if_any
ggplot2::diamonds %>%
  ## NB ! needs to come before if_any
  filter(!if_any(everything(), ~ grepl('V', .))) %>%
  head()
#> # A tibble: 6 × 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

## or with if_all
ggplot2::diamonds %>%
  filter(if_all(everything(), ~ !grepl('V', .))) %>%
  head()
#> # A tibble: 6 × 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

筛选任意列满足条件的行

## The new syntax makes it also easy to positively filter rows 
## where one columns fulfils a condition
ggplot2::diamonds %>%
  filter(if_any(everything(), ~ grepl('V',.))) %>%
  head()
#> # A tibble: 6 × 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.29 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

2
你知道为什么 diamonds %>% filter(across(everything(), ~grepl('V', .))) 返回一个空的 tibble 吗?我认为只需删除 ! 就可以返回任何列中包含 V 的所有行吗? - Dylan Russell
@DylanRussell 很抱歉回复晚了。我认为这很明显 - 使用 everything 意味着你正在寻找在所有列中都有“V”的行。 - tjebo
~ !grepl 有什么用途?你能告诉我吗? - Daman deep
我认为当应用于所有数据时,~是一个函数调用。 - Dre Day
1
这个可以使用 if_any()if_all() 进行更新。 - moodymudskipper
@moodymudskipper 谢谢,我有时会惊讶于dplyr的快速发展。我会在一秒钟内更新这个。 - tjebo

1
基于Akruns' suggestion的进一步选项 - 使用rowSums和subset创建逻辑向量。仅使用基本R。当列恰好包含我们正在查找的值时,这尤其有用且优雅。(或者如果您可以使用简单的条件语句创建二维数组,如下所示:df1 == "no_data"
## this is very easy when the expected value is EXACTLY the string
df1 <- structure(list(time = c("1:00", "2:00", "no_data", "3:00"), speed = c("30", "no_data", "no_data", "50"), wheels = c("no_data", "18", "no_data", "18")), .Names = c("time", "speed", "wheels"), class = "data.frame", row.names = c(NA, -4L))
df1[rowSums(df1 == "no_data") == 0, , drop = FALSE]
#>   time speed wheels
#> 4 3:00    50     18

## it's a bit more verbose when the expected value just CONTAINS the string
mtcars$type <- rownames(mtcars)
mtcars[rowSums(apply(mtcars, 2, \(x) grepl('Toyota|Mazda', x))) > 0, , drop = FALSE] |> head()
#>                 mpg cyl  disp  hp drat    wt  qsec vs am gear carb
#> Mazda RX4      21.0   6 160.0 110 3.90 2.620 16.46  0  1    4    4
#> Mazda RX4 Wag  21.0   6 160.0 110 3.90 2.875 17.02  0  1    4    4
#> Toyota Corolla 33.9   4  71.1  65 4.22 1.835 19.90  1  1    4    1
#> Toyota Corona  21.5   4 120.1  97 3.70 2.465 20.01  1  0    3    1
#>                          type
#> Mazda RX4           Mazda RX4
#> Mazda RX4 Wag   Mazda RX4 Wag
#> Toyota Corolla Toyota Corolla
#> Toyota Corona   Toyota Corona

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接