R Studio – grepl сравнение столбца в dataframe с перечнем шаблонов

Вопрос или проблема

У меня есть столбец с названием “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)

Пояснение

  1. Первый метод использует вложенные функции lapply и sapply, чтобы проверить, совпадает ли хотя бы один элемент из df2.PATTERN с каждым элементом из df1.MATCH. Функция any возвращает TRUE, если хотя бы один из аргументов является истинным.

  2. Второй метод требует составления общего паттерна из всех элементов df2.PATTERN, что позволяет фильтровать df1.MATCH по этому паттерну. Это действительно предпочтительный подход при работе с большими наборами данных, так как обеспечивает значительно более высокую производительность.

Заключение

Оба подхода приведут к желаемому результату, однако второй метод с использованием регулярного выражения будет значительно быстрее при работе с большими датафреймами. Регулярные выражения могут показаться сложными, но они очень мощные и полезные для выполнения таких задач в R.

Оцените материал
Добавить комментарий

Капча загружается...