วิธีการจับคู่กึ่งเวกเตอร์ของสตริง (ใน R) อย่างไร


36

ฉันไม่แน่ใจว่าควรจะเรียกศัพท์นี้ได้อย่างไรดังนั้นโปรดแก้ไขให้ถูกต้องหากคุณรู้จักคำศัพท์ที่ดีขึ้น

ฉันมีสองรายการ หนึ่งใน 55 รายการ (เช่น: เวกเตอร์ของสตริง) อีก 92 รายการชื่อจะคล้ายกัน แต่ไม่เหมือนกัน

ฉันต้องการที่จะหาสิ่งที่ดีที่สุดสำหรับผู้สมัครsใน 92 รายการกับรายการใน 55 รายการ (จากนั้นผมก็จะผ่านมันไปและเลือกที่เหมาะสมถูกต้อง)

จะทำอย่างไร?

ความคิดเห็นที่ฉันมีต่อ:

  1. ดูรายการที่ตรงกันทั้งหมด (ใช้รายการที่ตรงกันหรือไม่)
  2. ลองใช้เมทริกซ์ระยะทางระหว่างสตริงเวกเตอร์ แต่ฉันไม่แน่ใจว่าจะกำหนดได้ดีที่สุดอย่างไร (จำนวนตัวอักษรเหมือนกันลำดับของสตริงล่ะ)

ดังนั้นแพคเกจ / ฟังก์ชั่น / สาขาการวิจัยที่เกี่ยวข้องกับงานดังกล่าวและอย่างไร

Update: นี่คือตัวอย่างของเวคเตอร์ที่ฉันต้องการจับคู่

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
สวัสดีทาล:> เนื่องจากชื่อเหล่านี้ดูเหมือนจะเป็นชื่อทางวิทยาศาสตร์ที่ปราศจากการพิมพ์ผิดฉันจะลองใช้ตัวชี้วัด Levenshtein ก่อน
user603

2
บางครั้งต่อมาstringdistแพ็คเกจดูเหมือนว่าจะเป็นทรัพยากรที่ดีที่สุดสำหรับสิ่งนี้
shabbychef

คำตอบ:


18

ฉันมีปัญหาที่คล้ายกัน (ดูที่นี่: https://stackoverflow.com/questions/2231993/merging-two-data-frames-using-fuzzy-approximate-string-matching-in-r )

คำแนะนำส่วนใหญ่ที่ฉันได้รับมีดังนี้

pmatch()และagrep(), grep(), grepl()สามฟังก์ชั่นว่าถ้าคุณใช้เวลาในการมองผ่านจะช่วยให้คุณมีความเข้าใจบางอย่างในการจับคู่สตริงตัวอย่างทั้งโดยประมาณสตริงหรือ regex โดยประมาณ

มันยากที่จะให้ตัวอย่างที่ยากแก่คุณถึงวิธีจับคู่มัน หากคุณสามารถให้ข้อมูลตัวอย่างแก่เราได้ฉันมั่นใจว่าเราจะสามารถแก้ไขปัญหาได้

ตัวเลือกอื่นที่ฉันพบว่าใช้งานได้ดีคือการทำให้สตริงเรียบtolower()และดูอักษรตัวแรกของแต่ละคำภายในสตริงแล้วเปรียบเทียบ บางครั้งก็ทำงานได้โดยไม่ต้องผูกปม จากนั้นมีสิ่งที่ซับซ้อนกว่าเช่นระยะทางที่กล่าวถึงในคำตอบอื่น ๆ บางครั้งงานเหล่านี้บางครั้งพวกเขาน่ากลัว - มันขึ้นอยู่กับสตริง

เราเห็นพวกเขาได้ไหม

ปรับปรุง

ดูเหมือนว่า agrep () จะทำการหลอกลวงส่วนใหญ่เหล่านี้ โปรดทราบว่า agrep () เป็นเพียงการนำไปใช้ของ Levenshtein distance

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

บางคนไม่คำนวณแม้ว่าฉันไม่แน่ใจว่า Ferroplasm acidaramus เหมือนกับ Ferroglobus placidus DSM 10642 ตัวอย่างเช่น:

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

ฉันคิดว่าคุณอาจเป็น SOL เล็กน้อยสำหรับบางคนและบางทีการสร้างดัชนีตั้งแต่เริ่มต้นเป็นทางออกที่ดีที่สุด เช่น ,. สร้างตารางที่มีหมายเลขรหัสสำหรับ vec55 จากนั้นสร้างการอ้างอิงถึง ID ใน vec55 ใน vec91 ด้วยตนเอง ฉันรู้ว่าเจ็บปวด แต่ก็สามารถทำได้หลายอย่างด้วย agrep ()


สวัสดีแบรนดอน - ฉันเพิ่มตัวอย่างข้อมูล ขอบคุณ!
Tal Galili

สวัสดี Brandon - โซลูชันของคุณใช้งานได้ดีมาก - ขอบคุณ
Tal Galili

+1 สำหรับลิงก์ไปยังคำถามก่อนหน้านี้ในหัวข้อใน SE (thaks สำหรับตัวชี้ไปยัง agrep ())
user603

15

มีหลายวิธีในการวัดระยะทางระหว่างสองสาย วิธีการสำคัญสองมาตรฐาน (มาตรฐาน) ที่ใช้กันอย่างแพร่หลายใน R คือ Levenshtein และ Hamming distance อดีตมีอยู่ในแพ็คเกจ 'MiscPsycho' และอันหลังใน 'e1071' การใช้สิ่งเหล่านี้ฉันก็แค่คำนวณเมทริกซ์ระยะห่างแบบคู่จำนวน 92 คูณ 55 จากนั้นดำเนินการต่อจากที่นั่น (นั่นคือการจับคู่ที่ดีที่สุดสำหรับสตริง "1" ในรายการ 1 คือสตริง "x" จากรายการ 2 ")

อีกทางหนึ่งมีฟังก์ชั่นการเปรียบเทียบ () ในแพ็คเกจ RecordLinkage ที่ดูเหมือนว่าได้รับการออกแบบมาเพื่อทำสิ่งที่คุณต้องการและใช้ระยะทางที่เรียกว่า Jaro-Winkler ซึ่งดูเหมือนเหมาะสมกว่าสำหรับงานที่ทำอยู่ แต่ฉันไม่เคยมีประสบการณ์กับมัน .

แก้ไข: ฉันกำลังแก้ไขคำตอบของฉันเพื่อรวมความคิดเห็นของแบรนดอนและรหัสของ Tal เพื่อหาคู่ที่ตรงกับ "Aeropyrum pernix" รายการแรกของvec55 :

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

8
+1 นอกจากนี้ในกรณีที่เป็นประโยชน์คำว่า google เมื่อเปรียบเทียบสตริงคือ "แก้ไขระยะทาง": en.wikipedia.org/wiki/Edit_distance
ars

@ars:> ขอบคุณนั่นเป็นรายการที่สะดวกในการป้อนเข้าสู่เครื่องมือค้นหา R และดูว่าเกิดอะไรขึ้น!
user603

2
ระยะการแก้ไข Levenshtein ถูกนำไปใช้เป็นส่วนหนึ่งของแพ็คเกจพื้นฐานผ่าน agrep ()
Brandon Bertelsen

คำตอบที่ดี Kwak - ฉันจะได้ดูมันในอนาคต!
Tal Galili

โดยส่วนตัวแล้วฉันรู้สึกว่านี่เป็นคำตอบที่สมบูรณ์กว่าสำหรับคำถามของ Tal +1 สำหรับการชี้ RecordLinkage ของเรา - แน่นอนว่าฉันต้องลอง
Brandon Bertelsen

7

เพื่อเสริมคำตอบที่เป็นประโยชน์ของ Kwak ให้ฉันเพิ่มหลักการและแนวคิดง่ายๆ วิธีที่ดีในการกำหนดตัวชี้วัดคือการพิจารณาว่าสตริงอาจแตกต่างจากเป้าหมายอย่างไร "แก้ไขระยะทาง" มีประโยชน์เมื่อรูปแบบคือการรวมกันของข้อผิดพลาดในการพิมพ์เช่นการย้ายเพื่อนบ้านหรือพิมพ์ผิดคีย์เดียว

อีกวิธีที่มีประโยชน์ (ด้วยปรัชญาที่แตกต่างกันเล็กน้อย) คือการแมปสตริงทั้งหมดเป็นตัวแทนหนึ่งเดียวของคลาสของสตริงที่เกี่ยวข้อง วิธีการ " Soundex " ทำสิ่งนี้: รหัส Soundex สำหรับคำคือลำดับของอักขระสี่ตัวที่เข้ารหัสพยัญชนะหลักและกลุ่มของผลลัพธ์ภายในที่คล้ายกันที่ทำให้เกิดเสียง มันถูกใช้เมื่อคำที่สะกดผิดการออกเสียงหรือการแปรผันของอีกคนหนึ่ง ในแอปพลิเคชันตัวอย่างคุณจะดึงคำเป้าหมายทั้งหมดที่มีรหัส Soundex เท่ากับรหัส Soundex สำหรับแต่ละคำโพรบ (อาจมีเป้าหมายเป็นศูนย์หรือหลายเป้าหมายที่เรียกมาด้วยวิธีนี้)


3

ฉันขอแนะนำให้คุณดูN-grams และระยะทางDamerau – Levenshteinนอกเหนือจากคำแนะนำอื่น ๆ ของ Kwak

บทความนี้เปรียบเทียบความถูกต้องของระยะทางแก้ไขต่าง ๆ ที่กล่าวถึงในที่นี้ (และอ้างอิงจาก Google scholar อย่างมาก)

อย่างที่คุณเห็นมีหลายวิธีในการเข้าถึงสิ่งนี้และคุณยังสามารถรวมตัวชี้วัดที่แตกต่างกันได้ ฉันคิดว่า Levenshtein และตัวชี้วัดที่เกี่ยวข้องนั้นใช้งานง่ายที่สุดโดยเฉพาะอย่างยิ่งหากมีข้อผิดพลาดเกิดขึ้นเนื่องจากการพิมพ์ของมนุษย์ N-grams นั้นง่ายและเหมาะสมสำหรับข้อมูลที่ไม่ใช่ชื่อหรือคำต่อคำพูด

ในขณะที่ Soundex เป็นตัวเลือก แต่งานเล็ก ๆ น้อย ๆ ที่ฉันได้เห็น (ซึ่งเป็นที่ยอมรับในปริมาณน้อยมาก) soundex ไม่ทำงานเช่นเดียวกับ Levenshstein หรือระยะแก้ไขอื่น ๆ สำหรับชื่อที่ตรงกัน และ Soundex นั้น จำกัด เพียงวลีการออกเสียงที่พิมพ์โดยมนุษย์ในขณะที่ Levenshtein และ N-grams มีขอบเขตที่กว้างขึ้น (โดยเฉพาะ N-Gram แต่ฉันคาดหวังว่าระยะทางของ Levenshtein จะทำงานได้ดีขึ้นสำหรับคำที่ไม่ใช่คำ)

ฉันไม่สามารถช่วยได้เท่าแพ็คเกจ แต่แนวคิดของ N-grams นั้นค่อนข้างเรียบง่าย (ฉันทำแมโคร SPSS เพื่อทำ N-grams เมื่อเร็ว ๆ นี้ แต่สำหรับโครงการขนาดเล็กฉันจะไปกับแพ็คเกจที่สร้างขึ้นแล้วใน R โปสเตอร์อื่น ๆ ได้แนะนำ) นี่คือตัวอย่างของการคำนวณระยะทาง Levenshtein ในหลาม


ขอบคุณ Andy - ฉันจะได้ดูมันในอนาคต
Tal Galili

1

ฉันค้นคว้าแพ็คเกจและวิธีการแก้ปัญหานี้และฉันคิดว่าผู้สมัครที่ดีที่สุดคือfuzzywuzzyRแพ็คเกจ

แพคเกจ fuzzywuzzyR คือการจับคู่สตริงฟัซซี่ที่ดำเนินการของแพ็กเกจหลามfuzzywuzzy มันใช้ระยะทาง Levenshtein เพื่อคำนวณความแตกต่างระหว่างลำดับ รายละเอียดเพิ่มเติมเกี่ยวกับฟังก์ชั่นของ fuzzywuzzyR สามารถพบได้ในบล็อกโพสต์และในแพคเกจบทความสั้น ๆ

ฉันทำวิธีแก้ปัญหาอย่างง่ายสำหรับปัญหาของคุณ แต่มีเรื่องเล็กน้อย คุณต้องติดตั้ง python และถ้าคุณใช้ winodows ก็ต้องติดตั้งเครื่องมือ build สำหรับ Visual Studioด้วย คุณต้องเลือกสิ่งเหล่านี้:

  • Windows 10 sdk 10.0.17763.0 และ MSVC v140
  • VS 2015 C ++ บิวด์ทูล (v 14v00)

การแก้ปัญหานั้นง่าย ฟังก์ชั่นหลักExtractOneส่งคืนรายการค่าสองค่า อันดับแรกคือหนึ่งการจับคู่สตริงและอันดับที่สองคือคะแนนที่สอดคล้องกัน (ในช่วง 0 - 100) fuzzywuzzyRแพคเกจให้ยังฟังก์ชั่นอื่น ๆ ที่จะเป็นประโยชน์ เอกสารหลักสามารถพบได้ที่นี่ ฉันหวังว่ารหัสนี้จะช่วยแก้ปัญหา

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)   
}

เอาท์พุท:

[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

ขึ้นอยู่กับฟังก์ชั่น adist

คำนวณระยะทางสตริงโดยประมาณระหว่างเวกเตอร์อักขระ ระยะทางเป็นระยะทาง Levenshtein ทั่วไป (แก้ไข) ทำให้จำนวนการแทรกการลบและการแทนที่ที่น้อยที่สุดอาจจำเป็นต้องเปลี่ยนสตริงหนึ่งไปเป็นอีกสตริงหนึ่ง

ฟังก์ชั่นstringdistจากแพคเกจที่มีชื่อเดียวกันมีหลายวิธี (ดู?stringdist):

method = c ("osa", "lv", "dl", "hamming", "lcs", "qgram", "cosine", "jaccard", "jw", "soundex")

ด้วยสิ่งนี้คุณสามารถเลือกความแตกต่างสูงสุด (เกณฑ์):

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

DataFrame นี้แสดงเวกเตอร์แรกในคอลัมน์ firstvector ที่ดีที่สุดของ secondvector ในการจับคู่คอลัมน์ระยะทางในคอลัมน์ divergence และการจับคู่ที่สำคัญทั้งหมดที่เรียงลำดับในการเรียงคอลัมน์ตรงใน OP


2
แม้ว่าการใช้งานมักจะถูกผสมกับเนื้อหาสาระในคำถาม แต่เราควรจะเป็นเว็บไซต์สำหรับให้ข้อมูลเกี่ยวกับสถิติการเรียนรู้ของเครื่อง ฯลฯ ไม่ใช่รหัส มันอาจเป็นการดีที่จะให้รหัสเช่นกัน แต่โปรดอธิบายคำตอบที่สำคัญของคุณเป็นข้อความสำหรับผู้ที่ไม่ได้อ่านภาษานี้ดีพอที่จะรับรู้และแยกคำตอบจากรหัส
gung - Reinstate Monica
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.