R: Найти позицию символа, где дифференцируются 2 строки - PullRequest
1 голос
/ 14 февраля 2020

Я ищу способ найти, является ли конкатенированная строка в одном кадре данных точно такой же в другом - если она не найдена или отличается, то я хочу вернуть позицию, в которой строки различаются.

Пример:

DF 1

Store      Good  Price   concatenated <br>
1          Apple    1.50   1Apple1.50 <br>
3          Banana   3.50  3Banana3.50 <br>
5          Turkey   6.25  5Turkey6.25 <br>

DF 2

Store     Good      Price  concatenated <br>
1         Apple     1.80    1Apple1.80 <br>
2         Banana    3.50    2Banana3.50 <br>
5         Turkey    6.25    5Turkey6.25 <br>

Итак, я хочу вернуться:

Для Apple верните 9, потому что «1Apple1.50 и 1Apple1.80» различаются в 9-й позиции строки
Для банана верните 1, потому что «3Banana3.50 и 2Banana2.50» дифференцируются в 1-й позиции строки
Для Турции ничего не возвращать, потому что строки совпадают

Ответы [ 5 ]

2 голосов
/ 14 февраля 2020

В base R мы можем разделить строку с помощью strsplit и сделать сравнение соответствующих элементов list и получить позицию первого несоответствия с which

mapply(function(x, y) which(x != y)[1], 
     strsplit(df1$concatenated, ""), strsplit(df2$concatenated, ""))
#[1]  9  1 NA
* 1007. * данные
df1 <- structure(list(Store = c(1L, 3L, 5L), Good = c("Apple", "Banana", 
"Turkey"), Price = c(1.5, 3.5, 6.25), concatenated = c("1Apple1.50", 
"3Banana3.50", "5Turkey6.25")), class = "data.frame", row.names = c(NA, 
-3L))

df2 <- structure(list(Store = c(1L, 2L, 5L), Good = c("Apple", "Banana", 
"Turkey"), Price = c(1.8, 3.5, 6.25), concatenated = c("1Apple1.80", 
"2Banana3.50", "5Turkey6.25")), class = "data.frame", row.names = c(NA, 
-3L))
1 голос
/ 14 февраля 2020

Вот некоторые тесты различных подходов, предложенных до сих пор для 1000 строк длиной 1000. Вариант подхода Акруна, который опирается на charToRaw() вместо strsplit(), кажется самым быстрым.

Unit: milliseconds
     expr         min          lq       mean     median         uq        max neval cld
 strsplit   38.626212   38.868298   42.48461   42.43953   45.87118   46.32892    10  a 
 distance 6437.995304 6783.890225 6968.59793 7016.14651 7184.29213 7319.84097    10   b
  bitwXor   12.065822   12.278691   17.32897   18.52006   19.95079   26.90822    10  a 
    toRaw    4.572742    4.693785    7.76573    4.87048   10.73064   18.74406    10  a 

Код для воспроизведения:

set.seed(5)
size <- 1000
cvec <-  replicate(size, paste0(sample(letters, size, replace = TRUE), collapse = ""))
cvec2 <- sapply(cvec, function(x)  `substring<-`(x, {y <- sample(size, 1)}, y, sample(letters, 1)))

microbenchmark::microbenchmark(
  strsplit  = mapply(function(x, y) which(x != y)[1],
                     strsplit(cvec, ""), strsplit(cvec2, "")),
  distance = {res <- mapply(function(x, y)
    grepRaw(diag(attr(adist(x, y, counts = TRUE), "trafos")), pattern = "S|D|I"),
    cvec,
    cvec2, USE.NAMES = FALSE)
  res[lengths(res) == 0] <- NA
  unlist(res)},
  bitwXor = {res <- mapply(function(x,y) which(bitwXor(utf8ToInt(x),utf8ToInt(y))>0), cvec,cvec2, USE.NAMES = FALSE)
  res[lengths(res) == 0] <- NA
  unlist(res)},
  toRaw = mapply(function(x, y) which(charToRaw(x) != charToRaw(y))[1],
                 cvec, cvec2, USE.NAMES = FALSE),
  times = 10, check = "equal") 
1 голос
/ 14 февраля 2020

Базовое решение R с использованием bitwXor + utf8ToInt

> mapply(function(x,y) which(bitwXor(utf8ToInt(x),utf8ToInt(y))>0), df1$concatenated,df2$concatenated)
$`1Apple1.50`
[1] 9

$`3Banana3.50`
[1] 1

$`5Turkey6.25`
integer(0)
0 голосов
/ 14 февраля 2020

Другой вариант base R может быть:

mapply(function(x, y)
      grepRaw(diag(attr(adist(x, y, counts = TRUE), "trafos")), pattern = "S|D|I"),
      df1$concatenated,
      df2$concatenated)

$`1Apple1.50`
[1] 9

$`3Banana3.50`
[1] 1

$`5Turkey6.25`
integer(0)

Здесь он возвращает позицию, в которой символ был заменен, удален или вставлен, на основе расстояния Левенштейна.

Если мог быть несколько изменений:

mapply(function(x, y) 
 grepRaw(diag(attr(adist(x, y, counts = TRUE), "trafos")), pattern = "S|D|I", all = TRUE),
 df1$concatenated,
 df2$concatenated)
0 голосов
/ 14 февраля 2020

Вы можете напрямую сравнить два фрейма данных, например, используя all.equal()

library(dplyr)

df1 <- structure(list(Store = c(1L, 3L, 5L), Good = c("Apple", "Banana","Turkey"), Price = c(1.5, 3.5, 6.25), concatenated = c("1Apple1.50","3Banana3.50", "5Turkey6.25")), class = "data.frame", row.names = c(NA,-3L))    
df2 <- structure(list(Store = c(1L, 2L, 5L), Good = c("Apple", "Banana","Turkey"), Price = c(1.8, 3.5, 6.25), concatenated = c("1Apple1.80", "2Banana3.50", "5Turkey6.25")), class = "data.frame", row.names = c(NA,-3L))

# By default, ordering of rows and columns ignored
# But those can be overridden if desired
# By default all_equal is sensitive to variable differences
# But you can request dplyr convert similar types

all.equal(df1, df2, ignore_col_order = FALSE, ignore_row_order = FALSE)

[1] "Component “Store”: Mean relative difference: 0.3333333" "Component “Price”: Mean relative difference: 0.2"      
[3] "Component “concatenated”: 2 string mismatches" 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...