Вопрос или проблема
У меня есть столбец с названием “MATCH” в датафрейме и список шаблонов с названием “PATTERN”.
df1.MATCH <- c("ABC", "abc" ,"BCD")
df1 <- as.data.frame(df1.MATCH)
df2.PATTERN <- c("ABC", "abc", "ABC abc")
Я хочу использовать grepl для сравнения столбца MATCH со PATTERN, если это правда, я применю свои функции. Желаемым результатом будет “ABC” соответствует “ABC” и “ABC abc”. Это код, который я использовал:
df1 %>% filter(grepl(df1.MATCH,df2.PATTERN ))%>% ...
Я получаю ошибку:
"Сообщение об ошибке: В grepl(TXN_GROUP, parm[3]) : аргумент 'pattern' имеет длину > 1, и будет использоваться только первый элемент"
Я понимаю, что не могу использовать grepl для списка векторов. Есть ли способ это решить?
Кратко:
grepl
ожидает, что его первый аргумент будет строкой (длинной 1), а не вектором. Вы можете решить это с помощью комбинацийsapply
иlapply
(см. ниже), но вам будет лучше использовать одно регулярное выражение, которое захватывает то, что вы хотите сопоставить вdf1.MATCH
, и вовсе не использоватьdf2.PATTERN
. Этот второй вариант намного быстрее (хотя менее понятно) для большого набора данных. Для этого типа работы стоит научиться использовать регулярные выражения на полную мощь.
df1 %>% filter(grepl(pattern = "^((ABC)( )*)+$", x = df1.MATCH, ignore.case = TRUE))
Объяснение
Документация для grepl
показывает следующее использование:
grepl(pattern, x, ignore.case = FALSE, perl = FALSE,
fixed = FALSE, useBytes = FALSE)
Аргумент pattern
идет первым, и этот аргумент должен быть строкой (один элемент). Вы предоставляете df1.MATCH
этому аргументу, который является вектором.
Мы могли бы использовать sapply
, чтобы применить grepl
к каждому элементу df1.MATCH
.
sapply(df1.MATCH, grepl, x = df2.PATTERN)
ABC abc BCD
[1,] TRUE FALSE FALSE
[2,] FALSE TRUE FALSE
[3,] TRUE TRUE FALSE
Однако взгляните на результат! Вы, вероятно, не хотели матрицу. Что происходит, когда мы запускаем ваш grepl
только для первого элемента df1.MATCH
?
grepl("ABC",df2.PATTERN)
[1] TRUE FALSE TRUE
Мы получаем вектор, потому что grepl
проверяет ABC
на каждом элементе df2.PATTERN
. Чтобы получить полезный логический вектор для фильтрации, вам нужно вернуть логический вектор той же длины, что и df1.MATCH
. Я вижу два способа это сделать.
Метод 1: Используйте
any
Поскольку вы хотите узнать, какие элементы в df1.MATCH
соответствуют любым элементам в df2.PATTERN
, вы можете использовать any
, который возвращает TRUE
, если любой элемент в его аргументах является TRUE
. Нам нужно немного другую синтаксис, чтобы это сработало. Нам нужно обернуть grepl
в lapply
, чтобы сделать список из трех векторов (по одному для каждого элемента в df1.MATCH
), который подается в sapply
с обернутым any
. Если мы просто используем sapply
, any
вернет только одно значение, поскольку у нас есть входная матрица.
any(grepl("ABC", df2.PATTERN))
[1] TRUE
sapply(
lapply(df1.MATCH, grepl, x = df2.MATCH),
any)
[1] TRUE TRUE FALSE
Метод 2: Напишите лучшее регулярное выражение.
Вы хотите сопоставить содержимое df1.MATCH
с возможными значениями, которые выглядят как abc
, ABC
, ABC ABC
или ABC abc
и т.д. Вы можете охватить все это в одной строке регулярного выражения. Строка, которая вам нужна, это
"^((ABC)( )*)+$"
^ # Ничего не должно быть перед этим
(ABC) # Должно содержать ABC вместе как группу
( )* # за которым может следовать любое количество пробелов (включая 0)
((ABC)( )*)+ # Ищите шаблон ABC (пробел), повторяющийся один или более раз
$ # И ничего больше не должно быть после этого
Затем используйте grepl
с ignore.case = TRUE
:
grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE)
[1] TRUE TRUE FALSE
Бенчмаркинг
В большом наборе данных один из этих методов будет работать быстрее. Давайте выясним. Ваши результаты бенчмаркинга будут варьироваться в зависимости от ресурсов вашей машины.
df1.MATCH <- sample(c("ABC", "abc" ,"BCD"), size = 100000, replace = TRUE)
df1 <- data.frame(df1.MATCH)
df2.PATTERN <- c("ABC", "abc", "ABC abc")
library(rbenchmark)
benchmark("any lapply" = {
df1 %>%
filter(sapply(lapply(df1.MATCH, grepl, x=df2.PATTERN), any) )
},
"better regex" = {
df1 %>%
filter(grepl("^((ABC)( )*)+$", df1.MATCH, ignore.case = TRUE))
}
)
test replications elapsed relative user.self sys.self user.child sys.child
1 any lapply 100 149.13 70.678 147.67 0.39 NA NA
2 better regex 100 2.11 1.000 2.10 0.02 NA NA
Похоже, что улучшенный метод регулярных выражений значительно быстрее. Это потому, что он выполняет только одну операцию на строку (grepl
) перед фильтрацией. Другой метод выполняет четыре операции на строку: lapply
выполняет grepl
три раза (по одному для каждого элемента df2.PATTERN
), а затем sapply
выполняет any
для каждого элемента списка (каждой строки).
Ответ или решение
Для решения проблемы сопоставления столбца "MATCH" в датафрейме с заданным списком шаблонов "PATTERN" в R с помощью функции grepl
, нам необходимо учитывать, что grepl
принимает только один элемент в качестве первого аргумента. Поскольку вы пытаетесь сравнить вектор значений с другим вектором шаблонов, необходимо изменить подход.
Подход 1: Использование функции any
Поскольку вы хотите узнать, какие элементы из df1.MATCH
совпадают с любыми элементами из df2.PATTERN
, можно использовать функцию any
вместе с sapply
и lapply
. Это позволяет проверить, соответствует ли хотя бы один из шаблонов каждому значению из df1.MATCH
.
library(dplyr)
# Ваши данные
df1.MATCH <- c("ABC", "abc", "BCD")
df1 <- data.frame(df1.MATCH)
df2.PATTERN <- c("ABC", "abc", "ABC abc")
# Фильтрация по совпадениям
result <- df1 %>%
filter(sapply(lapply(df1.MATCH, function(x) grepl(x, df2.PATTERN, ignore.case = TRUE)), any))
print(result)
Подход 2: Использование регулярного выражения
Для более эффективного решения, особенно при больших наборах данных, лучше использовать одно регулярное выражение, которое будет охватывать все возможные варианты совпадений.
Вот пример подхода с использованием регулярного выражения:
# Ваши данные
df1.MATCH <- c("ABC", "abc", "BCD")
df1 <- data.frame(df1.MATCH)
df2.PATTERN <- c("ABC", "abc", "ABC abc")
# Объединение элементов df2.PATTERN в одно регулярное выражение
pattern <- paste0(paste(df2.PATTERN, collapse = "|"), collapse = "|")
# Фильтрация данных
result <- df1 %>%
filter(grepl(pattern, df1.MATCH, ignore.case = TRUE))
print(result)
Пояснение
-
Первый метод использует вложенные функции
lapply
иsapply
, чтобы проверить, совпадает ли хотя бы один элемент изdf2.PATTERN
с каждым элементом изdf1.MATCH
. Функцияany
возвращаетTRUE
, если хотя бы один из аргументов является истинным. -
Второй метод требует составления общего паттерна из всех элементов
df2.PATTERN
, что позволяет фильтроватьdf1.MATCH
по этому паттерну. Это действительно предпочтительный подход при работе с большими наборами данных, так как обеспечивает значительно более высокую производительность.
Заключение
Оба подхода приведут к желаемому результату, однако второй метод с использованием регулярного выражения будет значительно быстрее при работе с большими датафреймами. Регулярные выражения могут показаться сложными, но они очень мощные и полезные для выполнения таких задач в R.