Сопоставление двух наборов данных с помощью нечеткого совпадения строк "многие к одному" в R

У меня есть два больших набора данных (500 тыс. Единиц в каждом), которые я хотел бы связать посредством нечеткого сопоставления строк с именами отдельных лиц, но используя также информацию о других переменных. Проблема похожа на описанную здесь: Как я могу сопоставить строки нечеткого совпадения из двух наборов данных?

Однако решение, опубликованное там, требует сначала произвести все парные потенциальные совпадения с помощью expand.grid но с моими данными это не может быть сделано. Уже, если у вас есть два набора данных по 10000 единиц измерения каждый, это приводит к общему набору данных в 100 000 000 потенциальных парных совпадений.

Я хотел бы сначала выполнить слияние много-к-одному, в котором наблюдения $k$ из набора данных A сопоставляются с 5 наиболее близкими совпадающими наблюдениями из набора данных B (судя по расстоянию строки Джаро Винклера), которые попадают в определенное возрастная группа, скажем, плюс / минус 5 лет.

Например, если $k$ в данных A

name          birthyear
John Smith    1984

и другие наблюдения в наборе данных B

serial   name           birthyear
1        John Smith     1983
2        Sara Pinkert   1973
3        John Smyth     1999
4        John Smithe    1985
5        John Smith     1984
6        Jon Smith      1984

тогда пять "лучших" совпадений для $k$ с наблюдениями в данных B должны быть obs number 1, 4, 5, 6 для ограничения года рождения +-5 лет. В этом случае нет. 2 (Сара Пинкерт) не должна совпадать из-за имени, и нет. 3 (Джон Смит) не должен совпадать, потому что год рождения для этого наблюдения слишком поздно.

Функции и команды, предоставляемые другими библиотеками, такими как fastLink, stringdist, или же recordLinkage хороши и быстры, но они всегда дают только однозначные совпадения (и они редко имеют возможность включать информацию из диапазона года рождения, чтобы ограничить измерение проблемы сопоставления).

Закрытое решение, которое я смог выяснить до сих пор, заключается в использовании compare.linkage функция от recordLinkage но опция блокировки (blockfld), кажется, блокирует строго определенную переменную, поэтому не совсем очевидно, как можно использовать диапазон для информации о году рождения:

rpairs = compare.linkage(dataA, 
                     dataB, 
                     blockfld = c("birthyear"), 
                     identity1 = dataA$id1, 
                     identity2 = dataB$id2, 
                     n_match = 5, 
                     strcmpfun = jarowinkler)

Но это только блокирует идеальный год рождения, следовательно, он вернул бы два матча, которые были бы "нет". 5 и 6 (Джон Смит 1984, Джон Смит 1984).

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

name1 = c("John Smith", "Adam Bower", "Felix von Epstein", "Charles Sawyer", "Benjamin Hoynes")
yob1 = c(1980, 1977, 1981, 1981, 1978)
dataA = data.frame(name1, yob1)

name2 = c("Jon Smyth", "Perry Bower", "Felix Epstein", "Terry Barnes", "John Smith", "Benamin Hoynes", "Frank Sawyer", "Charles Sawer", "Charles Sauer", "Philip Smith", "Franklin Sawyer", "Jonathan Smith", "Gabriel Bars", "Aron Bow", "Harry Haynes")
yob2 = c(1981, 1983, 1981, 1982, 1983, 1980, 1980, 1986, 1982, 1978, 1977, 1981, 1979, 1975, 1980)
dataB = data.frame(name2, yob2)

1 ответ

Отредактировано для дополнительного кода на основе комментариев

Может быть, это поможет вам

Ваши данные

name1 = c("John Smith", "Adam Bower", "Felix von Epstein", "Charles Sawyer", "Benjamin Hoynes")
yob1 = c(1980, 1977, 1981, 1981, 1978)
dataA = data.frame(name1, yob1)

name2 = c("Jon Smyth", "Perry Bower", "Felix Epstein", "Terry Barnes", "John Smith", "Benamin Hoynes", "Frank Sawyer", "Charles Sawer", "Charles Sauer", "Philip Smith", "Franklin Sawyer", "Jonathan Smith", "Gabriel Bars", "Aron Bow", "Harry Haynes")
yob2 = c(1981, 1983, 1981, 1982, 1983, 1980, 1980, 1986, 1982, 1978, 1977, 1981, 1979, 1975, 1980)
dataB = data.frame(name2, yob2)

Функция для приблизительного соответствия строк и возрастной фильтрации

top_five_amatch <- function(A_row, B) {
                require(stringdist)
                ans <- intersect(order(stringdist(A_row$name1, dataB$name2, method="jw")), which(abs(A_row$yob1 - dataB$yob2) <= 5))
                return(head(ans, 5))
            }

В его сердце

library(stringdist)
order(stringdist(dataA$name1[1], dataB$name2, method="jw"))     # order of string-distance
# [1]  5  1 12 10 14  7  8  9  6 11  3  2  4 15 13

which(abs(dataA$yob1[1] - dataB$yob2) <= 5)                     # age band filter
# [1]  1  2  3  4  5  6  7  9 10 11 12 13 14 15 

intersect из 2 сохранит только те значения, которые присутствуют после фильтрации по возрастному диапазону


Главный
Захватить индексы ближайшего совпадения в строке dataA

I <- lapply(seq_len(nrow(dataA)), function(i) top_five_amatch(dataA[i,], dataB))
# [[1]]
# [1]  5  1 12 10 14

# [[2]]
# [1] 14  7  1  4  6

# [[3]]
# [1]  3  1  2  6 11

# [[4]]
# [1]  8  9  7 11  2

# [[5]]
# [1]  6 15  4  2 11

Лучшие 5 матчей для каждого ряда dataA

matchB <- dataB[unlist(I), ]
               # name2 yob2
# 5         John Smith 1983
# 1          Jon Smyth 1981
# 12    Jonathan Smith 1981
# 10      Philip Smith 1978
# 14          Aron Bow 1975
# 14.1        Aron Bow 1975
# 7       Frank Sawyer 1980
# 1.1        Jon Smyth 1981
# 4       Terry Barnes 1982
# 6     Benamin Hoynes 1980
# 3      Felix Epstein 1981
# 1.2        Jon Smyth 1981
# 2        Perry Bower 1983
# 6.1   Benamin Hoynes 1980
# 11   Franklin Sawyer 1977
# 8      Charles Sawer 1986
# 9      Charles Sauer 1982
# 7.1     Frank Sawyer 1980
# 11.1 Franklin Sawyer 1977
# 2.1      Perry Bower 1983
# 6.2   Benamin Hoynes 1980
# 15      Harry Haynes 1980
# 4.1     Terry Barnes 1982
# 2.2      Perry Bower 1983
# 11.2 Franklin Sawyer 1977

Чтобы сохранить в "широком" формате с несколькими столбцами, попробуйте что-то вроде

matchB <- lapply(I, function(i) dataB[i,])
Reduce("cbind", matchB)
            # name2 yob2           name2 yob2           name2 yob2
# 5      John Smith 1983    Frank Sawyer 1980   Felix Epstein 1981
# 1       Jon Smyth 1981 Franklin Sawyer 1977  Benamin Hoynes 1980
# 12 Jonathan Smith 1981        Aron Bow 1975     Perry Bower 1983
# 10   Philip Smith 1978  Benamin Hoynes 1980    Terry Barnes 1982
# 14       Aron Bow 1975    Gabriel Bars 1979 Franklin Sawyer 1977
             # name2 yob2           name2 yob2
# 5    Charles Sawer 1986  Benamin Hoynes 1980
# 1    Charles Sauer 1982 Franklin Sawyer 1977
# 12 Franklin Sawyer 1977    Harry Haynes 1980
# 10    Frank Sawyer 1980    Terry Barnes 1982
# 14    Gabriel Bars 1979   Felix Epstein 1981
Другие вопросы по тегам