Сопоставление двух наборов данных с помощью нечеткого совпадения строк "многие к одному" в 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