Слияние через нечеткое сопоставление переменных в R

У меня есть два фрейма данных (x и y), где идентификаторы student_name, father_name и mother_name. Из-за типографских ошибок ( "n" вместо "m", случайных белых пробелов и т.д.) У меня есть около 60% значений, которые не выравниваются, хотя я могу видеть данные и видеть их. Есть ли способ уменьшить уровень несоответствия так или иначе, чтобы вручную редактировать, по крайней мере, возможно? Кадры данных имеют около 700 тыс. Наблюдений.

R было бы лучше. Я знаю немного python и некоторые базовые инструменты unix. Постскриптум Я прочитал на agrep(), но не понимаю, как это может работать с фактическими наборами данных, особенно если совпадение превышает более чем одну переменную.


update (данные для размещенной награды):

Здесь представлены два примера фреймов данных sites_a и sites_b. Их можно сопоставить с числовыми столбцами lat и lon, а также в столбце sitename. Было бы полезно знать, как это можно сделать на a) просто lat + lon, b) sitename или c) оба.

вы можете указать файл test_sites.R, который вывешен как сущность.

В идеале ответ заканчивается на

merge(sites_a, sites_b, by = **magic**)

Ответы

Ответ 1

Функция agrep (часть базы R), которая делает приближенное соответствие строк, используя расстояние редактирования Levenshtein, вероятно, стоит попробовать. Не зная, как выглядят ваши данные, я не могу предложить рабочее решение. Но это предложение... Он записывает совпадения в отдельный список (если есть несколько одинаково хороших совпадений, то они также записываются). Скажем, что ваш data.frame называется df:

l <- vector('list',nrow(df))
matches <- list(mother = l,father = l)
for(i in 1:nrow(df)){
  father_id <- with(df,which(student_name[i] == father_name))
  if(length(father_id) == 1){
    matches[['father']][[i]] <- father_id
  } else {
    old_father_id <- NULL
    ## try to find the total                                                                                                                                 
    for(m in 10:1){ ## m is the maximum distance                                                                                                             
      father_id <- with(df,agrep(student_name[i],father_name,max.dist = m))
      if(length(father_id) == 1 || m == 1){
        ## if we find a unique match or if we are in our last round, then stop                                                                               
        matches[['father']][[i]] <- father_id
        break
      } else if(length(father_id) == 0 && length(old_father_id) > 0) {
        ## if we can't do better than multiple matches, then record them anyway                                                                              
        matches[['father']][[i]] <- old_father_id
        break
      } else if(length(father_id) == 0 && length(old_father_id) == 0) {
        ## if the nearest match is more than 10 different from the current pattern, then stop                                                                
        break
      }
    }
  }
}

Код для mother_name будет в основном одинаковым. Вы могли бы даже объединить их в цикл, но этот пример предназначен только для иллюстрации.

Ответ 2

Это принимает список общих имен столбцов, совпадений на основе agrep всех этих столбцов вместе, а затем, если all.x или all.y равно TRUE, он добавляет несоответствующие записи, заполняющие отсутствующие столбцы с NA. В отличие от merge, имена столбцов, которые должны совпадать, должны быть одинаковыми в каждом кадре данных. Кажется, что проблема заключается в том, чтобы правильно установить параметры agrep, чтобы избежать ложных совпадений.

  agrepMerge <- function(df1, df2, by, all.x = FALSE, all.y = FALSE, 
    ignore.case = FALSE, value = FALSE, max.distance = 0.1, useBytes = FALSE) {

    df1$index <- apply(df1[,by, drop = FALSE], 1, paste, sep = "", collapse = "")
    df2$index <- apply(df2[,by, drop = FALSE], 1, paste, sep = "", collapse = "")

    matches <- lapply(seq_along(df1$index), function(i, ...) {
      agrep(df1$index[i], df2$index, ignore.case = ignore.case, value = value,
            max.distance = max.distance, useBytes = useBytes)
    })

    df1_match <- rep(1:nrow(df1), sapply(matches, length))
    df2_match <- unlist(matches)

    df1_hits <- df1[df1_match,]
    df2_hits <- df2[df2_match,]

    df1_miss <- df1[setdiff(seq_along(df1$index), df1_match),]
    df2_miss <- df2[setdiff(seq_along(df2$index), df2_match),]

    remove_cols <- colnames(df2_hits) %in% colnames(df1_hits)

    df_out <- cbind(df1_hits, df2_hits[,!remove_cols])

    if(all.x) {
      missing_cols <- setdiff(colnames(df_out), colnames(df1_miss))
      df1_miss[missing_cols] <- NA
      df_out <- rbind(df_out, df1_miss)
    }
    if(all.x) {
      missing_cols <- setdiff(colnames(df_out), colnames(df2_miss))
      df2_miss[missing_cols] <- NA
      df_out <- rbind(df_out, df2_miss)
    }
    df_out[,setdiff(colnames(df_out), "index")]
}