Сравнить и связать строки с различными порядками слов / количеством слов - PullRequest
0 голосов
/ 18 ноября 2018

Я пытаюсь использовать пакет recordLinkage, чтобы связать вместе два набора данных, где один набор данных имеет тенденцию давать несколько фамилий / отличий, а другой просто дает одну фамилию. В настоящее время используемой функцией сравнения строк является функция Jaro-Winkler, однако возвращаемая оценка зависит от того, как строки совпадают случайно, а не от того, содержится ли содержимое более короткой строки где-либо в более длинной строке. Это приводит к тому, что создается много ссылок низкого качества. Воспроизводимый пример неправильных взвешиваний таков:

library(RecordLinkage)
data1 <- as.data.frame(list("lname" = c("lolli gaggen nazeem", "lolli gaggen nazeem", "lolli gaggen nazeem"),
                           "bday" = c("1908-08-08", "1979-12-12", "1560-06-06") ) )

data2 <- as.data.frame(list("lname" = c("lolli", "gaggen", "nazeem"),
                           "bday" = c("1908-08-08", "1979-12-12", "1560-06-06") ) )

blocking_variable <- c("bday")
pass <- compare.linkage(data1, data2, blockfld = blocking_variable, strcmp = T)
pass_weights <- epiWeights(pass)
getPairs(pass_weights, single.rows = TRUE)

  id1              lname.1     bday.1 id2 lname.2     bday.2    Weight
1   1 lolli gaggen nazheem 1908-08-08   1   lolli 1908-08-08 0.9162463
2   2 lolli gaggen nazheem 1979-12-12   2  gaggen 1979-12-12 0.8697165
3   3 lolli gaggen nazheem 1560-06-06   3 nazheem 1560-06-06 0.6995502

Я хочу, чтобы идентификаторы 2 и 3 получали примерно те же веса, что и идентификатор № 1, однако в настоящее время они намного ниже, поскольку их фамилии не находятся в одинаковом положении в обоих наборах данных (хотя содержание совпадает). Есть ли способ, которым я могу изменить используемую здесь функцию сравнения строк / структуру данных, чтобы я мог учесть различные порядки?

Дополнительные примечания:

  • Оба набора данных имеют миллионы строк, поэтому эффективность использования памяти безусловно, важно здесь!

  • Иногда в другом наборе данных может быть больше, чем один последний имя, поэтому мы будем сравнивать 3 слова против 2 слов - вероятно, будет лучше всего начать сначала с простого случая

  • Чаще всего в именах будут различия в написании между двумя наборами данных
  • В настоящее время мы используем этап качества IBM для этой связи и они используют функцию сравнения "MULT_UNCERT" (https://www.ibm.com/support/knowledgecenter/en/SSZJPZ_11.7.0/com.ibm.swg.im.iis.ds.design.help.doc/topics/r_qresfgde_MULT_UNCERT_comparison.html). Я хочу повторить это в R.

Ответы [ 2 ]

0 голосов
/ 11 февраля 2019

Дополнение, которое я сделал к ответу Хайенеса, как указано в комментарии:

library(gtools)

...

# Store the split up components of each comparison variable.
split1 <- strsplit(block_pairs[["lname.x"]], split)
split2 <- strsplit(block_pairs[["lname.y"]], split)

# Recombine tokens into all possible orderings:
make_combinations <- function(x) {
      # Use permutations from the gtools package
      split_names <- permutations(length(x),length(x),x)
      apply(X=split_names, MARGIN=1, FUN=paste0, collapse=' ')
}

split1 <- lapply(X=split1, FUN=`make_combinations`)
split2 <- lapply(X=split2, FUN=`make_combinations`)

# Perform jarowinkler comparisons on each string combination and append it to the table
block_pairs[ ,("winkler.lname") := mapply(function(x, y) max(outer(x, y, jarowinkler)), split1, split2)]

# Sort by the jarowinkler score
block_pairs <- block_pairs[order(winkler.lname)]

# 0.85 is an appropriate threshold in this instance
block_pairs <- block_pairs[winkler.lname >= 0.85]


      bday           lname.x             lname.y    winkler.lname
1: 1908-08-08  lolli gaggen nazeem         lolli     0.8526316
2: 1560-06-06  lolli gaggen nazeem        nazeem     0.8631579
3: 1979-12-12  lolli gaggen nazeem        gaggen     0.8631579
4: 1979-12-12           matt dowle        m dowl     0.9200000
5: 1560-06-06           john-smith  johnny smith     0.9666667
0 голосов
/ 14 декабря 2018

Задумывались ли вы о следующем подходе?

Записать связь и имена, как я знаю, вы знаете, сложно. В идеале вы хотите заблокировать другую доступную информацию (пол, уникальные идентификаторы, dob, информацию о местоположении и т. Д.), А затем выполнить сравнение строк в именах.

Вы упоминаете большие наборы данных с миллионами записей. Смотрите не дальше, чем пакет data.table от великого Мэтта Доула (https://stackoverflow.com/users/403310/matt-dowle).

Пакет RecordLinkage медленный по сравнению. Вы можете легко улучшить приведенный ниже код, чтобы подумать о методах хеширования строк, используя soundex, двойной метафон, nysiis и т. Д.

# install.packages("data.table")
library(RecordLinkage)
library(data.table)

data1 <- as.data.frame(list("lname" = c("lolli gaggen nazeeem", "lolli gaggen nazeem", "lollly gaggen nazeem", "matt dowle", "john-smith"),
                           "bday" = c("1908-08-08", "1979-12-12", "1560-06-06", "1979-12-12", "1560-06-06") ) )

data2 <- as.data.frame(list("lname" = c("lolli", "gaggen", "nazeem", "m dowl", "johnny smith"),
                           "bday" = c("1908-08-08", "1979-12-12", "1560-06-06", "1979-12-12", "1560-06-06") ) )


# Coerce to data.tables
setDT(data1)
setDT(data2)

# Define a regex split (we will split all words based on space or hyphen)
split <- " |-"

# Apply a blocking strategy based on bday. Ideally your dataset would allow for additional blocking strategies(?).
block_pairs <- merge(data1, data2, by = "bday", all = T,
            sort = TRUE, suffixes = c(".x", ".y"))

# Store the split up components of each comparison variable.
split1 <- strsplit(block_pairs[["lname.x"]], split)
split2 <- strsplit(block_pairs[["lname.y"]], split)

# Perform jarowinkler comparisons on each combination of components of each string
fc <- jarowinkler(block_pairs[["lname.x"]], block_pairs[["lname.y"]])
pc <- mapply(function(x, y) max(outer(x, y, jarowinkler)), split1, split2)

# Store the max of the full and partial comparisons
block_pairs[, ("winkler.lname") := mapply(function(x,y) max(x,y), fc, pc)]


# Sort by the jarowinkler score
block_pairs <- block_pairs[order(winkler.lname)]

# Inspect
block_pairs

# 0.96 is an appropriate threshold in this instance
block_pairs <- block_pairs[winkler.lname >= 0.96]
...