Подход № 1: Точное совпадение
Почему бы не что-то вроде
library(stringr)
library(dplyr)
a <- a %>%
# Extract first and last names into new variables
mutate(First_Name = str_extract(Name, "^[A-z]+"),
Last_Name = str_extract(Name, "[A-z]+$"),)
# Inner Join by first and last name.
# Add a suffix to be able to distinguish the origin of columns.
b %>% inner_join(a, by = c("First_Name", "Last_Name"), suffix = c(".b", ".a")) %>%
# Select the columns you want to see.
# Note that only the colums that have an ambiguous name have a suffix.
select(ID.a, Name, Gender.a, Country, First_Name, Last_Name, ID.b)
Отлично работает, если вы ищете только точные совпадения.При желании вы также можете извлечь второе имя из строки через str_extract(string, "[^A-z]+[A-z]+[^A-z$]")
.
Результат:
ID.a Name Gender.a Country First_Name Last_Name ID.b
1 3 Dana Benton F USA Dana Benton 2
Подход № 2: Расстояние до слова (Джаро-Винклер)
Расширение с этого великого поста :
library(RecordLinkage)
library(dplyr)
lookup <- expand.grid(target = a$Name, source = b$Whole_Name, stringsAsFactors = FALSE)
lookup %>% group_by(target) %>%
mutate(match_score = jarowinkler(target, source)) %>%
summarise(match = match_score[which.max(match_score)], matched_to = ref[which.max(match_score)]) %>%
inner_join(b, c("matched_to" = "Whole_Name"))
Все, что выше 0,8 или 0,9 должно быть хорошим соответствием.Все еще не идеально.Вы можете попытаться сопоставить имя и фамилию отдельно, если ваши данные чистые.
Результат:
# A tibble: 4 x 8
target match matched_to ID First_Name Last_Name Third_Name Gender
<chr> <dbl> <chr> <dbl> <chr> <chr> <chr> <chr>
1 Dana Benton 1 Dana Benton 2 Dana Benton NA F
2 Ella Collins 0.593 Matt King 4 Matt King NA M
3 James Alan 0.667 Gary Allan Cole 1 Gary Cole Allan M
4 Mattias Adams 0.792 Matt King 4 Matt King NA M
Подход № 3: Расстояние до слова (Левенштейн)
То же, что и выше, только с использованием расстояния Левенштейна и which.min()
library(RecordLinkage)
library(dplyr)
lookup <- expand.grid(target = a$Name, source = b$Whole_Name, stringsAsFactors = FALSE)
lookup %>% group_by(target) %>%
mutate(match_score = levenshteinDist(target, source)) %>%
summarise(match = match_score[which.min(match_score)], matched_to = ref[which.min(match_score)]) %>%
inner_join(b, c("matched_to" = "Whole_Name"))
Как и ожидалось, это дает худшую производительность, чем JW.
Результат:
# A tibble: 4 x 8
target match matched_to ID First_Name Last_Name Third_Name Gender
<chr> <int> <chr> <dbl> <chr> <chr> <chr> <chr>
1 Dana Benton 0 Dana Benton 2 Dana Benton NA F
2 Ella Collins 9 Dana Benton 2 Dana Benton NA F
3 James Alan 8 Matt King 4 Matt King NA M
4 Mattias Adams 8 Matt King 4 Matt King NA M
Данные
a <- structure(list(ID = c(1, 2, 3, 4), Name = c("Mattias Adams", "James Alan", "Dana Benton", "Ella Collins"), Gender = c("M", "M", "F", "F"), Country = c("UK", "Canada", "USA", "USA")), .Names = c("ID", "Name", "Gender", "Country"), row.names = c(NA, -4L), class = "data.frame")
b <- structure(list(ID = c(1, 2, 3, 4), First_Name = c("Gary", "Dana", "Lena", "Matt"), Last_name = c("Cole", "Benton", "Jamison", "King"), Third_Name = c("Allan", "NA", "Anne", "NA"), Whole_name = c("Gary Allan Cole", "Dana Benton", "Lena Anne Jamison", "Matt King"), Gender = c("M", "F", "F", "M")), .Names = c("ID", "First_Name", "Last_Name", "Third_Name", "Whole_Name", "Gender"), row.names = c(NA, -4L), class = "data.frame")