Заказать независимое сопоставление строк в R

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

Иллюстрация:

#Table A
word <- c("PILLAY NOLAN VICTOR", "PILLAY NICHOLAS")
#Master Table
choices <- c("IGOR JOSE VICTOR","WILLIAM NICHOLAS","NOLAN PILLAY","NICHOLAS PILLAY")

Выполнение приведенного ниже кода:

data <- NULL

    df <- foreach(a = idivix(length(word),chunks = no_cores),  .combine = "rbind", .packages = 'stringdist') %dopar% {
      do.call('rbind', lapply(seq(a$i,length.out = a$m), function(i)
      {
        tryCatch({
          #library(stringdist)
          d = expand.grid(word[i],choices)
          names(d) <- c("traveler_names","people_name")
          d$dist <-stringdist(d$traveler_names,d$people_name, method = "lv")
          d <- d[order(d$dist),]
          d <- d[1,]
          data<-rbind(data,d)
        }, error=function(e){})
      }))
    }

возвращает следующее совпадение:

traveler name           people name          dist
PILLAY NOLAN VICTOR     IGOR JOSE VICTOR     10
PILLAY NICHOLAS         WILLIAM NICHOLAS      3

вместо сопоставления с «NOLAN PILLAY» и «NICHOLAS PILLAY» из-за зависимости от порядка при сопоставлении строк.

Есть ли способ получить желаемый результат в R, в основном заказывать независимое сопоставление строк? Был бы очень признателен за помощь...


person chitvan gupta    schedule 25.02.2019    source источник
comment
Я бы просто создал новые столбцы со словами в алфавитном порядке. Затем сопоставьте их.   -  person Gregor Thomas    schedule 25.02.2019
comment
Вы говорите, что хотите отдавать приоритет сопоставлению фамилий, а не имени, которое должно отображаться в строке?   -  person jdobres    schedule 25.02.2019


Ответы (1)


Я обнаружил, что при большом количестве данных функция stringdist может увязнуть. Итак, если у вас возникнут проблемы со скоростью, есть другие варианты пакетов (например, пакет RecordLinkage, agrep) и другие методы сопоставления строк (т. е. другие меры расстояния). Кроме того, не на 100% понятно, о чем вы спрашиваете, но если ваша проблема заключается в том, что вы хотите проверить, чтобы перевернуть имя и фамилию, вы всегда можете использовать strsplit. Например,

> library(stringdist)
> 
> #Table A
> word <- c("PILLAY NOLAN VICTOR", "PILLAY NICHOLAS")
> #Master Table
> choices <- c("IGOR JOSE VICTOR","WILLIAM NICHOLAS","NOLAN PILLAY","NICHOLAS PILLAY")
> 
> # Try # 1
> match_dist <- sapply(word,
+        function(x) min(stringdist(x, choices, method = "lv")))
> 
> match_text <- sapply(word,
+        function(x) choices[which.min(stringdist(x, choices, method = "lv"))])
> 
> df <- data.frame("traveler name" = word,
+                  "people name" = match_text, 
+                  "dist" = match_dist, stringsAsFactors = FALSE, row.names = NULL)
> # Checking results
> df
        traveler.name      people.name dist
1 PILLAY NOLAN VICTOR IGOR JOSE VICTOR    9
2     PILLAY NICHOLAS WILLIAM NICHOLAS    3
> 
> 
> # Reversing srings, assuming names are sepearated by a space
> reversed <- sapply(strsplit(choices, " "), function(x) paste(rev(x), collapse=" ")) #reversing words
> choices <- c(choices, reversed)
> choices <- unique(choices)
> 
> 
> # Try # 2
> match_dist <- sapply(word,
+                      function(x) min(stringdist(x, choices, method = "lv")))
> 
> match_text <- sapply(word,
+                      function(x) choices[which.min(stringdist(x, choices, method = "lv"))])
> 
> df <- data.frame("traveler name" = word,
+                  "people name" = match_text, 
+                  "dist" = match_dist, stringsAsFactors = FALSE, row.names = NULL)
> 
> # Checking the new results
> df
        traveler.name     people.name dist
1 PILLAY NOLAN VICTOR    PILLAY NOLAN    7
2     PILLAY NICHOLAS PILLAY NICHOLAS    0

В зависимости от того, как настроены ваши данные, вам может быть полезно (или нет) избавиться от отчества или очистить данные другими способами, но это должно помочь вам начать работу.

РЕДАКТИРОВАТЬ:

Я протестировал несколько разных решений, но не тестировал agrep, так что, возможно, стоит проверить. Я бы определенно предпочел RecordLinkage и даже подумал бы о том, чтобы разбить ваш набор данных на идеальные совпадения и несовпадения, а затем только обратить (или отсортировать) несовпадения. Код будет узким местом при вычислении меры расстояния, поэтому все, что может уменьшить количество имен, нуждающихся в мере расстояния, вероятно, вам поможет.

> library(stringdist)
> library(RecordLinkage)
> library(microbenchmark)
> 
> #Table A
> word <- c("PILLAY NOLAN VICTOR", "PILLAY NICHOLAS", "WILLIAM NICHOLAS")
> #Master Table
> choices <- c("IGOR JOSE VICTOR","WILLIAM NICHOLAS","NOLAN PILLAY","NICHOLAS PILLAY")
> 
> microbenchmark({
+ 
+ # All reversed
+ reversed <- sapply(strsplit(choices, " "), function(x) paste(rev(x), collapse=" ")) #reversing words
+ choices1 <- c(choices, reversed)
+ choices1 <- unique(choices1)
+ 
+ match_dist <- sapply(word, function(x) min(stringdist(x, choices1, method = "lv")))
+ match_text <- sapply(word, function(x) choices1[which.min(stringdist(x, choices1, method = "lv"))])
+ 
+ df1 <- data.frame("traveler name" = word, 
+                  "people name" = match_text,
+                  "dist" = match_dist, 
+                  stringsAsFactors = FALSE, row.names = NULL)
+ }, 
+ 
+ {
+ # Record linkage
+ reversed <- sapply(strsplit(choices, " "), function(x) paste(rev(x), collapse=" ")) #reversing words
+ choices2 <- c(choices, reversed)
+ choices2 <- unique(choices2)
+   
+ match_dist2 <- sapply(word, function(x) min(levenshteinDist(x, choices2)))
+ match_text2 <- sapply(word, function(x) choices2[which.min(levenshteinDist(x, choices2))])
+   
+ df2 <- data.frame("traveler name" = word, 
+                   "people name" = match_text2,
+                   "dist" = match_dist2, 
+                   stringsAsFactors = FALSE, row.names = NULL)
+ },
+ 
+ {
+ # Sorted
+ 
+ sorted <- sapply(strsplit(choices, " "), function(x) paste(sort(x), collapse=" ")) #sorting choices
+ choices3 <- c(choices, sorted)
+ choices3 <- unique(choices3)
+ word3 <- sapply(strsplit(word, " "), function(x) paste(sort(x), collapse=" ")) #sorting words
+ 
+ match_dist3 <- sapply(word3, function(x) min(stringdist(x, choices3, method = "lv")))
+ match_text3 <- sapply(word3, function(x) choices3[which.min(stringdist(x, choices3, method = "lv"))])
+ 
+ df3 <- data.frame("traveler name" = word3, 
+                   "people name" = match_text3,
+                   "dist" = match_dist3, 
+                   stringsAsFactors = FALSE, row.names = NULL)
+ },
+ times = 1)
Unit: milliseconds


    expr          min       lq     mean   median       uq      max neval
revers     6.627258 6.627258 6.627258 6.627258 6.627258 6.627258     1
reversRL   4.016632 4.016632 4.016632 4.016632 4.016632 4.016632     1
sort       7.223453 7.223453 7.223453 7.223453 7.223453 7.223453     1
> 
> all.equal(df1, df2)
[1] TRUE
> 
> df2
        traveler.name      people.name dist
1 PILLAY NOLAN VICTOR     PILLAY NOLAN    7
2     PILLAY NICHOLAS  PILLAY NICHOLAS    0
3    WILLIAM NICHOLAS WILLIAM NICHOLAS    0
> df3
        traveler.name      people.name dist
1 NOLAN PILLAY VICTOR     NOLAN PILLAY    7
2     NICHOLAS PILLAY  NICHOLAS PILLAY    0
3    NICHOLAS WILLIAM NICHOLAS WILLIAM    0
person Andrew    schedule 25.02.2019
comment
Спасибо, Андрей! Данные огромны, и я пытаюсь сопоставить их, поэтому необходимо выполнять параллельные вычисления. Во-вторых, данные, содержащие имена, очень нерегулярны, не везде имя начинается с фамилии, в некоторых местах имена все еще начинаются с имени, и они идеально совпадают. Таким образом, очень сложно определить из огромного списка, где имя начинается с фамилии, поэтому не удалось применить strsplit и найти решение, в котором порядок имени не имеет значения. Я пробовал все методы в stringddist, но везде соответствие неверно из-за порядка имен. - person chitvan gupta; 26.02.2019
comment
Действительно много данных. К сожалению, я не думаю, что вы можете обойти перестановку имен в той или иной форме (либо путем сортировки, как предложил @Gregor, либо путем изменения строк). Я опубликую редактирование через секунду, но использование RecordLinkage для меня было намного быстрее (и я предполагаю, что оно более эффективно). Кроме того, расстояние Яровинклера обычно хорошо работает для имен, если вы хотите попробовать это. - person Andrew; 26.02.2019