Làm thế nào để phù hợp với hai vectơ của chuỗi (trong R)?


36

Tôi không chắc chắn làm thế nào điều này nên được gọi là, vì vậy xin vui lòng sửa cho tôi nếu bạn biết một thuật ngữ tốt hơn.

Tôi đã có hai danh sách. Một trong 55 mục (ví dụ: một vectơ của chuỗi), phần còn lại của 92. Tên các mục tương tự nhưng không giống nhau.

Tôi muốn tìm ra ứng viên tốt nhất s trong 92 danh sách các mục trong danh sách 55 (sau đó tôi sẽ đi qua nó và chọn phù hợp chính xác).

Nó được hoàn thiện bằng cách nào?

Ý tưởng tôi đã có nơi:

  1. Xem tất cả những cái phù hợp (sử dụng danh sách gì đó phù hợp?)
  2. Hãy thử một ma trận khoảng cách giữa các vectơ chuỗi, nhưng tôi không chắc cách xác định tốt nhất (số lượng chữ cái giống hệt nhau, còn thứ tự của chuỗi thì sao?)

Vì vậy, gói / chức năng / lĩnh vực nghiên cứu nào liên quan đến một nhiệm vụ như vậy, và làm thế nào?

Cập nhật: Đây là một ví dụ về các vectơ tôi muốn khớp

vec55 <- c("Aeropyrum pernix", "Archaeoglobus fulgidus", "Candidatus_Korarchaeum_cryptofilum", 
"Candidatus_Methanoregula_boonei_6A8", "Cenarchaeum_symbiosum", 
"Desulfurococcus_kamchatkensis", "Ferroplasma acidarmanus", "Haloarcula_marismortui_ATCC_43049", 
"Halobacterium sp.", "Halobacterium_salinarum_R1", "Haloferax volcanii", 
"Haloquadratum_walsbyi", "Hyperthermus_butylicus", "Ignicoccus_hospitalis_KIN4", 
"Metallosphaera_sedula_DSM_5348", "Methanobacterium thermautotrophicus", 
"Methanobrevibacter_smithii_ATCC_35061", "Methanococcoides_burtonii_DSM_6242"
)
vec91 <- c("Acidilobus saccharovorans 345-15", "Aciduliprofundum boonei T469", 
"Aeropyrum pernix K1", "Archaeoglobus fulgidus DSM 4304", "Archaeoglobus profundus DSM 5631", 
"Caldivirga maquilingensis IC-167", "Candidatus Korarchaeum cryptofilum OPF8", 
"Candidatus Methanoregula boonei 6A8", "Cenarchaeum symbiosum A", 
"Desulfurococcus kamchatkensis 1221n", "Ferroglobus placidus DSM 10642", 
"Halalkalicoccus jeotgali B3", "Haloarcula marismortui ATCC 43049", 
"Halobacterium salinarum R1", "Halobacterium sp. NRC-1", "Haloferax volcanii DS2", 
"Halomicrobium mukohataei DSM 12286", "Haloquadratum walsbyi DSM 16790", 
"Halorhabdus utahensis DSM 12940", "Halorubrum lacusprofundi ATCC 49239", 
"Haloterrigena turkmenica DSM 5511", "Hyperthermus butylicus DSM 5456", 
"Ignicoccus hospitalis KIN4/I", "Ignisphaera aggregans DSM 17230", 
"Metallosphaera sedula DSM 5348", "Methanobrevibacter ruminantium M1", 
"Methanobrevibacter smithii ATCC 35061", "Methanocaldococcus fervens AG86", 
"Methanocaldococcus infernus ME", "Methanocaldococcus jannaschii DSM 2661", 
"Methanocaldococcus sp. FS406-22", "Methanocaldococcus vulcanius M7", 
"Methanocella paludicola SANAE", "Methanococcoides burtonii DSM 6242", 
"Methanococcus aeolicus Nankai-3", "Methanococcus maripaludis C5", 
"Methanococcus maripaludis C6", "Methanococcus maripaludis C7", 
"Methanococcus maripaludis S2", "Methanococcus vannielii SB", 
"Methanococcus voltae A3", "Methanocorpusculum labreanum Z", 
"Methanoculleus marisnigri JR1", "Methanohalobium evestigatum Z-7303", 
"Methanohalophilus mahii DSM 5219", "Methanoplanus petrolearius DSM 11571", 
"Methanopyrus kandleri AV19", "Methanosaeta thermophila PT", 
"Methanosarcina acetivorans C2A", "Methanosarcina barkeri str. Fusaro", 
"Methanosarcina mazei Go1", "Methanosphaera stadtmanae DSM 3091", 
"Methanosphaerula palustris E1-9c", "Methanospirillum hungatei JF-1", 
"Methanothermobacter marburgensis str. Marburg", "Methanothermobacter thermautotrophicus str. Delta H", 
"Nanoarchaeum equitans Kin4-M", "Natrialba magadii ATCC 43099", 
"Natronomonas pharaonis DSM 2160", "Nitrosopumilus maritimus SCM1", 
"Picrophilus torridus DSM 9790", "Pyrobaculum aerophilum str. IM2", 
"Pyrobaculum arsenaticum DSM 13514", "Pyrobaculum calidifontis JCM 11548", 
"Pyrobaculum islandicum DSM 4184", "Pyrococcus abyssi GE5", "Pyrococcus furiosus DSM 3638", 
"Pyrococcus horikoshii OT3", "Staphylothermus hellenicus DSM 12710", 
"Staphylothermus marinus F1", "Sulfolobus acidocaldarius DSM 639", 
"Sulfolobus islandicus L.D.8.5", "Sulfolobus islandicus L.S.2.15", 
"Sulfolobus islandicus M.14.25", "Sulfolobus islandicus M.16.27", 
"Sulfolobus islandicus M.16.4", "Sulfolobus islandicus Y.G.57.14", 
"Sulfolobus islandicus Y.N.15.51", "Sulfolobus solfataricus P2", 
"Sulfolobus tokodaii str. 7", "Thermococcus gammatolerans EJ3", 
"Thermococcus kodakarensis KOD1", "Thermococcus onnurineus NA1", 
"Thermococcus sibiricus MM 739", "Thermofilum pendens Hrk 5", 
"Thermoplasma acidophilum DSM 1728", "Thermoplasma volcanium GSS1", 
"Thermoproteus neutrophilus V24Sta", "Thermosphaera aggregans DSM 11486", 
"Vulcanisaeta distributa DSM 14429", "uncultured methanogenic archaeon RC-I"
) 

2
Hi Tal:> Cho rằng đây có vẻ là những tên khoa học không có lỗi đánh máy, tôi sẽ thử số liệu Levenshtein trước (trong bối cảnh ma trận khoảng cách 92 đến 55) và xem nó xuất hiện như thế nào.
user603

2
Một thời gian sau, stringdistgói có vẻ như là tài nguyên tốt nhất cho loại điều này.
shabbychef

Câu trả lời:


19

Tôi đã có vấn đề tương tự. (xem tại đây: https://stackoverflow.com/questions/2231993/merging-two-data-frames-USE-fuzzy-approximate-opes-matching-in-r )

Hầu hết các khuyến nghị mà tôi nhận được rơi vào khoảng:

pmatch()agrep(), grep(), grepl()ba chức năng rằng nếu bạn dành thời gian để xem xét thông qua sẽ cung cấp cho bạn một số cái nhìn sâu sắc phù hợp với chuỗi gần đúng hoặc bằng chuỗi gần đúng hoặc gần đúng regex.

Không nhìn thấy các chuỗi, thật khó để cung cấp cho bạn ví dụ khó khăn về cách khớp chúng. Nếu bạn có thể cung cấp cho chúng tôi một số dữ liệu mẫu, tôi chắc chắn chúng tôi có thể đi đến một giải pháp.

Một tùy chọn khác mà tôi thấy hoạt động tốt là làm phẳng các chuỗi tolower(), nhìn vào chữ cái đầu tiên của mỗi từ trong chuỗi và sau đó so sánh. Đôi khi điều đó làm việc mà không gặp một trở ngại. Sau đó, có những điều phức tạp hơn như khoảng cách được đề cập trong các câu trả lời khác. Đôi khi những công việc này, đôi khi chúng thật kinh khủng - nó thực sự phụ thuộc vào chuỗi.

Chúng ta có thể nhìn thấy chúng?

Cập nhật

Có vẻ như agrep () sẽ thực hiện thủ thuật cho hầu hết những thứ này. Lưu ý rằng agrep () chỉ là triển khai khoảng cách Levenshtein của R.

agrep(vec55[1],vec91,value=T)

Một số người không tính toán mặc dù, tôi thậm chí không chắc chắn liệu Ferroplasm acidaramus có giống với Ferroglobus placidus DSM 10642 hay không, ví dụ:

agrep(vec55[7],vec91,value=T) 

Tôi nghĩ rằng bạn có thể là một chút SOL cho một số trong số này và có lẽ tạo ra một chỉ mục từ đầu là đặt cược tốt nhất. I E,. Tạo một bảng có số id cho vec55, sau đó tạo thủ công tham chiếu đến id trong vec55 trong vec91. Đau đớn, tôi biết, nhưng rất nhiều trong số đó có thể được thực hiện với agrep ().


Xin chào Brandon - Tôi đã thêm một mẫu dữ liệu. Cảm ơn!
Tal Galili

Xin chào Brandon - giải pháp của bạn đã làm việc tuyệt vời - cảm ơn bạn.
Tal Galili

+1 cho liên kết đến câu hỏi trước đó về chủ đề trong SE (thaks cho con trỏ tới agrep ()).
user603

15

Có nhiều cách để đo khoảng cách giữa hai chuỗi. Hai cách tiếp cận quan trọng (tiêu chuẩn) được triển khai rộng rãi trong R là Levenshtein và khoảng cách Hamming. Cái trước có sẵn trong gói 'MiscPologistso' và cái sau trong 'e1071'. Sử dụng chúng, tôi chỉ cần tính toán một ma trận 92 x 55 khoảng cách theo cặp, sau đó tiến hành từ đó (tức là kết quả phù hợp nhất cho chuỗi "1" trong danh sách 1 là chuỗi "x" từ danh sách 2 có khoảng cách nhỏ nhất đến chuỗi "1 ").

Ngoài ra, có một gói so sánh () trong gói RecordLinkage dường như được thiết kế để làm những gì bạn muốn và sử dụng khoảng cách gọi là Jaro-Winkler có vẻ phù hợp hơn cho nhiệm vụ trong tay, nhưng tôi chưa có kinh nghiệm với nó .

EDIT: Tôi đang chỉnh sửa câu trả lời của mình để bao gồm nhận xét của Brandon cũng như mã của Tal, để tìm một kết quả khớp với "Aeropyrum pernix", mục đầu tiên của vec55 :

agrep(vec55[1],vec91,ignore.case=T,value=T,max.distance = 0.1, useBytes = FALSE)
[1] "Aeropyrum pernix K1"

8
+1. Ngoài ra, trong trường hợp hữu ích, thuật ngữ google khi so sánh các chuỗi là "chỉnh sửa khoảng cách": en.wikipedia.org/wiki/Edit_distance
ars

@ars:> cảm ơn, đó là một danh sách tiện dụng để cung cấp cho công cụ tìm kiếm R và xem những gì xuất hiện!
user603

2
Khoảng cách chỉnh sửa Levenshtein được triển khai như một phần của gói cơ sở thông qua agrep ()
Brandon Bertelsen

Câu trả lời tuyệt vời Kwak - Tôi sẽ có một cái nhìn về nó trong tương lai!
Tal Galili

Cá nhân, tôi cảm thấy rằng đây là một câu trả lời đầy đủ hơn cho câu hỏi của Tal. +1 để trỏ RecordLinkage của chúng tôi - Tôi chắc chắn sẽ phải thử.
Brandon Bertelsen

7

Để bổ sung câu trả lời hữu ích của Kwak, cho phép tôi thêm một số nguyên tắc và ý tưởng đơn giản. Một cách tốt để xác định số liệu là bằng cách xem xét cách các chuỗi có thể thay đổi so với mục tiêu của chúng. "Chỉnh sửa khoảng cách" rất hữu ích khi biến thể là sự kết hợp của các lỗi chính tả như hoán đổi hàng xóm hoặc gõ sai một phím.

Một cách tiếp cận hữu ích khác (với một triết lý hơi khác nhau) là ánh xạ mỗi chuỗi thành một đại diện của một lớp các chuỗi liên quan. Các " Soundex phương pháp" thực hiện điều này: mã Soundex cho một từ là một chuỗi các ký tự mã hóa các Bốn phụ âm và nhóm hậu quả nội bộ tương tự nghe có vẻ chính. Nó được sử dụng khi các từ là lỗi chính tả ngữ âm hoặc các biến thể của nhau. Trong ứng dụng ví dụ, bạn sẽ tìm nạp tất cả các từ mục tiêu có mã Soundex bằng mã Soundex cho mỗi từ thăm dò. (Có thể có 0 hoặc nhiều mục tiêu được tìm nạp theo cách này.)


3

Tôi cũng đề nghị bạn kiểm tra N-gram và khoảng cách Damensau của Levenshtein bên cạnh những gợi ý khác của Kwak.

Bài viết này so sánh độ chính xác của một vài khoảng cách chỉnh sửa khác nhau được đề cập ở đây (và được trích dẫn cao theo học giả google).

Như bạn có thể thấy có nhiều cách khác nhau để tiếp cận điều này, và thậm chí bạn có thể kết hợp các số liệu khác nhau (bài báo tôi liên kết để nói về bitcoin này). Tôi nghĩ rằng Levenshtein và các số liệu dựa trên có liên quan có ý nghĩa trực quan nhất, đặc biệt là nếu xảy ra lỗi do đánh máy của con người. N-gram cũng đơn giản và có ý nghĩa đối với dữ liệu không phải là tên hoặc từ mỗi lần nói.

Mặc dù soundex là một tùy chọn, nhưng một chút công việc tôi đã thấy (được thừa nhận là một lượng rất nhỏ) soundex không hoạt động tốt như Levenshstein hoặc các khoảng cách chỉnh sửa khác cho các tên trùng khớp. Và Soundex chỉ giới hạn ở các cụm từ ngữ âm có thể được nhập bởi các máy đánh chữ của con người, trong đó Levenshtein và N-gram có phạm vi rộng hơn (đặc biệt là N-gram, nhưng tôi hy vọng khoảng cách Levenshtein cũng sẽ hoạt động tốt hơn đối với các từ không phải là từ).

Tôi không thể giúp gì cho các gói, nhưng khái niệm về N-gram khá đơn giản (tôi đã tạo một macro SPSS để thực hiện N-gram gần đây, nhưng đối với một dự án nhỏ như vậy tôi sẽ chỉ đi với các gói đã được tạo trong R các áp phích khác đã đề nghị). Dưới đây là một ví dụ về tính toán khoảng cách Levenshtein trong trăn.


Cảm ơn Andy - tôi sẽ có một cái nhìn về nó trong tương lai.
Tal Galili

1

Tôi đã nghiên cứu một số gói và cách giải quyết vấn đề này và tôi nghĩ ứng cử viên tốt nhất là fuzzywuzzyRgói.

Gói fuzzywuzzyR là một chuỗi mờ phù hợp với việc triển khai gói python fuzzywuzzy . Nó sử dụng Khoảng cách Levenshtein để tính toán sự khác biệt giữa các chuỗi. Thông tin chi tiết về chức năng của fuzzywuzzyR có thể được tìm thấy trong bài đăng trên blog và trong Vignette gói.

Tôi đã làm giải pháp đơn giản cho vấn đề của bạn, nhưng có một chút nắm bắt. Bạn phải cài đặt python và nếu bạn sử dụng winodows cũng phải cài đặt một số công cụ xây dựng cho visual studio . Bạn phải chọn những thứ này:

  • Windows 10 sdk 10.0.17763.0 và MSVC v140
  • Công cụ xây dựng VS 2015 C ++ (v 14v00)

Giải pháp rất đơn giản. Hàm chính ExtractOnetrả về danh sách hai giá trị. Đầu tiên là một chuỗi khớp và thứ hai là điểm tương ứng (trong phạm vi 0 - 100). Các fuzzywuzzyRgói phần mềm cũng cung cấp các chức năng khác mà có thể hữu ích. Tài liệu chính có thể tìm thấy ở đây . Tôi hy vọng mã này giúp giải quyết vấn đề.

library(fuzzywuzzyR)

# The Fuzzy initialization
init_proc = FuzzUtils$new()
PROC = init_proc$Full_process # class process-method
PROC1 = tolower # base R function
init_scor = FuzzMatcher$new()
SCOR = init_scor$WRATIO    
init <- FuzzExtract$new()

match_strings <- function(vector_to_process, base_vector){  
  new_vec = c()
  for(i in 1:length(vector_to_process)){      
    new_word <- init$ExtractOne(string = vector_to_process[i], sequence_strings = base_vector, processor = PROC1, scorer = SCOR, score_cutoff = 0L)
    new_vec[i] <- new_word[[1]]
  }     
  return(new_vec)
}

# Check if all python modules are available
if (check_availability()){    
  new_vec <- match_strings(vec55, vec91)
  print(new_vec)   
}

Đầu ra:

[1] "Aeropyrum pernix K1"                                 "Archaeoglobus fulgidus DSM 4304"                    
[3] "Candidatus Korarchaeum cryptofilum OPF8"             "Candidatus Methanoregula boonei 6A8"                
[5] "Cenarchaeum symbiosum A"                             "Desulfurococcus kamchatkensis 1221n"                
[7] "Thermoplasma volcanium GSS1"                         "Haloarcula marismortui ATCC 43049"                  
[9] "Halobacterium sp. NRC-1"                             "Halobacterium salinarum R1"                         
[11] "Haloferax volcanii DS2"                              "Haloquadratum walsbyi DSM 16790"                    
[13] "Hyperthermus butylicus DSM 5456"                     "Ignicoccus hospitalis KIN4/I"                       
[15] "Metallosphaera sedula DSM 5348"                      "Methanothermobacter thermautotrophicus str. Delta H"
[17] "Methanobrevibacter smithii ATCC 35061"               "Methanococcoides burtonii DSM 6242"       

0

Dựa trên chức năng adist

Tính khoảng cách chuỗi gần đúng giữa các vectơ ký tự. Khoảng cách là khoảng cách Levenshtein (chỉnh sửa) tổng quát, đưa ra số lần chèn, xóa và thay thế tối thiểu có thể có trọng số cần thiết để chuyển đổi một chuỗi thành chuỗi khác

Hàm stringdisttừ một gói cùng tên có một số phương thức (xem ?stringdist):

phương thức = c ("osa", "lv", "dl", "hamming", "lcs", "qgram", "cosine", "jaccard", "jw", "soundex")

Với điều này, bạn có thể chọn phân kỳ tối đa (ngưỡng):

firstvector<-vec55
secondvector<-vec91

match<-character()
threshold<-14 # max 14 characters of divergence
mindist<-integer()
sortedmatches<-character()

for (i in 1:length(firstvector) ) {
  matchdist<-adist(firstvector[i],secondvector)[1,]
  # matchdist<-stringdist(firstvector[i],secondvector) # several methods available

  matchdist<-ifelse(matchdist>threshold,NA,matchdist)
  sortedmatches[i]<-paste(secondvector[order(matchdist, na.last=NA)], collapse = ", ")
  mindist[i]<- tryCatch(ifelse(is.integer(which.min(matchdist)),matchdist[which.min(matchdist)],NA), error = function(e){NA})
  match[i]<-ifelse(length(secondvector[which.min(matchdist)])==0,NA,
                  secondvector[which.min(matchdist)] )
}
res<-data.frame(firstvector=firstvector,match=match,divergence=mindist, sortedmatches=sortedmatches, stringsAsFactors = F)
res

Khung dữ liệu này hiển thị vectơ đầu tiên trong phần đầu tiên của cột, phần tử tốt nhất của phần tử thứ hai trong cột khớp, khoảng cách của nó trong phân kỳ cột và tất cả các kết quả khớp được sắp xếp theo thứ tự cột được sắp xếp như trong OP.


2
Mặc dù việc triển khai thường được trộn lẫn với nội dung thực chất trong các câu hỏi, chúng tôi được cho là một trang web cung cấp thông tin về thống kê, học máy, v.v., không phải mã. Cũng có thể tốt khi cung cấp mã, nhưng vui lòng xây dựng câu trả lời chính xác của bạn trong văn bản cho những người không đọc ngôn ngữ này đủ tốt để nhận ra & trích xuất câu trả lời từ mã.
gung - Phục hồi Monica
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.