Comment faire correspondre presque deux vecteurs de chaînes (en R)?

36

Je ne suis pas sûr de la façon dont cela devrait être appelé, alors corrigez-moi si vous connaissez un meilleur terme.

J'ai deux listes. L'un des 55 éléments (par exemple: un vecteur de chaînes), l'autre de 92. Les noms des éléments sont similaires mais non identiques.

Je souhaite trouver les meilleurs candidats de la liste dans la liste 55 92 aux articles (je puis passer par là et choisissez le montage correct).

Comment ceci peut être fait?

Les idées que j'ai eues où:

  1. Voir tous ceux qui correspondent (en utilisant quelque chose? Correspondance)
  2. Essayez une matrice de distance entre les vecteurs de chaînes, mais je ne sais pas comment la définir au mieux (nombre de lettres identiques, qu'en est-il de l'ordre des chaînes?)

Alors, quel paquet / fonctions / domaine de recherche traite d’une telle tâche et comment?

Mise à jour: Voici un exemple des vecteurs que je souhaite associer

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"
) 
Tal Galili
la source
2
Bonjour Tal:> Étant donné que ces noms semblent être des noms scientifiques sans fautes de frappe, je voudrais d’abord essayer la métrique de Levenshtein (dans le contexte d’une matrice de distance de 92 sur 55) et voir comment elle se présentera.
user603
2
Quelque temps plus tard, le stringdistpaquet semble être la meilleure ressource pour ce genre de chose.
Shabbychef

Réponses:

19

J'ai eu des problèmes similaires. (vu ici: https://stackoverflow.com/questions/2231993/merging-two-data-frames-using-fuzzy-approximate-string-matching-in-r )

La plupart des recommandations que j'ai reçues se sont articulées autour de:

pmatch()Et agrep(), grep(), grepl()trois fonctions que si vous prenez le temps de regarder à travers vous donnera un aperçu de correspondance de chaîne approximative soit par chaîne approximative ou regex approximative.

Sans voir les chaînes, il est difficile de vous donner un exemple concret de la façon de les faire correspondre. Si vous pouviez nous fournir des exemples de données, je suis sûr que nous pourrions trouver une solution.

Une autre option que j'ai trouvée fonctionne bien est d'aplatir les chaînes, tolower()en regardant la première lettre de chaque mot de la chaîne, puis en comparant. Parfois, cela fonctionne sans accroc. Ensuite, il y a des choses plus complexes comme les distances mentionnées dans d'autres réponses. Parfois, ces travaux, parfois ils sont horribles - cela dépend vraiment des cordes.

Peut-on les voir?

Mise à jour

Il semble qu'agendp () fasse l'affaire pour la plupart d'entre eux. Notez que Agrep () est juste l'implémentation de la distance de Levenshtein par R.

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

Certains ne calculent pas bien, cependant, je ne suis même pas sûr si Ferroplasm acidaramus est identique à Ferroglobus placidus DSM 10642, par exemple:

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

Je pense que vous pouvez être un peu SOL pour certains d'entre eux et peut-être créer un index à partir de zéro est le meilleur pari. c'est à dire,. Créez une table avec des identifiants pour vec55, puis créez manuellement une référence à l'identifiant dans vec55 dans vec91. Douloureux, je sais, mais cela peut être fait en grande partie avec Agrep ().

Brandon Bertelsen
la source
Bonjour Brandon - J'ai ajouté un échantillon des données. Merci!
Tal Galili
Bonjour Brandon - votre solution a très bien fonctionné - merci.
Tal Galili
+1 pour le lien vers la question précédente sur le sujet en SE (thaks pour le pointeur sur Agrep ()).
user603
15

Il existe de nombreuses façons de mesurer les distances entre deux chaînes. Deux approches importantes (standard) largement mises en œuvre dans R sont la distance de Levenshtein et de Hamming. Le premier est disponible dans le paquet «MiscPsycho» et le dernier dans «e1071». En utilisant ceux-ci, je calculerais simplement une matrice 92 sur 55 de distances par paires, puis procéderais à partir de là (c'est-à-dire que la meilleure correspondance possible pour la chaîne "1" dans la liste 1 est la chaîne "x" de la liste 2 avec la plus petite distance à la chaîne "1 ").

Sinon, il y a une fonction de comparaison () dans RecordLinkage paquet qui semble être conçu pour faire ce que vous voulez et utilise le soi - disant Jaro-Winkler la distance qui semble plus approprié pour la tâche à accomplir, mais je l' ai eu aucune expérience avec elle .

EDIT: Je modifie ma réponse pour inclure le commentaire de Brandon ainsi que le code de Tal, afin de trouver une correspondance avec "Aeropyrum pernix", la première entrée de vec55 :

agrep(vec55[1],vec91,ignore.case=T,value=T,max.distance = 0.1, useBytes = FALSE)
[1] "Aeropyrum pernix K1"
utilisateur603
la source
8
+1 De plus, au cas où cela serait utile, le terme "google" lors de la comparaison de chaînes est "modification de la distance": en.wikipedia.org/wiki/Edit_distance
ars
@ars:> merci, c’est une liste pratique pour alimenter un moteur de recherche R et voir ce qui en sort!
user603
2
La distance de montage de Levenshtein est implémentée dans le package de base via Agrep ()
Brandon Bertelsen
Excellente réponse Kwak - Je vais y jeter un coup d'oeil!
Tal Galili
Personnellement, j'estime qu'il s'agit d'une réponse plus complète à la question de Tal. +1 pour avoir pointé notre RecordLinkage - Je vais certainement devoir l'essayer.
Brandon Bertelsen
7

Pour compléter la réponse utile de Kwak, permettez-moi d'ajouter quelques principes et idées simples. Un bon moyen de déterminer la métrique consiste à déterminer comment les chaînes peuvent différer de la cible. "Modifier la distance" est utile lorsque la variante est une combinaison d'erreurs typographiques telles que la transposition de voisins ou la mauvaise saisie d'une clé unique.

Une autre approche utile (avec une philosophie légèrement différente) consiste à mapper chaque chaîne en un représentant d'une classe de chaînes liées. La méthode " Soundex " fait ceci: le code Soundex pour un mot est une séquence de quatre caractères codant la consonne principale et des groupes de conséquence interne de son similaire. Il est utilisé lorsque des mots sont mal orthographiés ou des variantes phonétiques. Dans l'exemple d'application, vous récupérez tous les mots cibles dont le code Soundex est égal au code Soundex de chaque mot de sonde. (Il pourrait y avoir zéro ou plusieurs cibles récupérées de cette façon.)

whuber
la source
3

Je vous suggère également de consulter N-grammes et la distance Damerau – Levenshtein en plus des autres suggestions de Kwak.

Cet article compare la précision de quelques distances de montage différentes mentionnées ici (et est très cité selon Google scholar).

Comme vous pouvez le constater, il existe de nombreuses manières d’aborder ceci, et vous pouvez même combiner différentes métriques (le document que j’ai lié à des discussions à ce sujet un peu). Je pense que Levenshtein et les métriques associées ont le sens le plus intuitif, en particulier si des erreurs surviennent à cause du typage humain. Les n-grammes sont également simples et ont un sens pour des données autres que des noms ou des mots.

Bien que soundex soit une option, le peu de travail que j'ai vu (ce qui est certes une très petite quantité) soundex ne fonctionne pas aussi bien que Levenshstein ou d'autres distances d'édition pour les noms correspondants. Et le Soundex est limité aux phrases phonétiques probablement entrées par des caractères humains, où Levenshtein et N-grammes ont une portée potentiellement plus large (en particulier N-gram, mais je m'attendrais à ce que la distance de Levenshtein soit également meilleure pour les non-mots).

Je ne peux pas vous aider en ce qui concerne les paquets, mais le concept de N-grammes est assez simple (j’ai créé une macro SPSS pour faire N-grammes récemment, mais pour un si petit projet, j’aimerais simplement utiliser les paquets déjà créés dans R les autres affiches ont suggéré). Voici un exemple de calcul de la distance de Levenshtein en python.

Andy W
la source
Merci Andy - Je vais y jeter un coup d'oeil dans le futur.
Tal Galili
1

J'ai recherché des paquets et des moyens de résoudre ce problème et je pense que le meilleur candidat est le fuzzywuzzyRpaquet.

Le package fuzzywuzzyR est une chaîne fuzzy correspondant à l’implémentation du package python fuzzywuzzy . Il utilise la distance de Levenshtein pour calculer les différences entre les séquences. Plus de détails sur les fonctionnalités de fuzzywuzzyR peuvent être trouvés dans le blog-post et dans le paquet Vignette.

J'ai fait la solution simple pour votre problème, mais il y a un petit piège. Vous devez installer python et si vous utilisez winodows, vous devez également installer des outils de construction pour visual studio . Vous devez choisir ceux-ci:

  • Windows 10 sdk 10.0.17763.0 et MSVC v140
  • Outils de génération VS 2015 C ++ (v 14v00)

La solution est simple La fonction principale ExtractOnerenvoie la liste de deux valeurs. Le premier correspond à une correspondance de chaîne et le second correspond au score correspondant (compris entre 0 et 100). Le fuzzywuzzyRpaquet fournit également d'autres fonctions qui peuvent être utiles. La documentation principale peut être trouvée ici . J'espère que ce code aide à résoudre le problème.

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

Sortie:

[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"       
Peteruherek
la source
0

Basé sur la fonction adist

Calcule la distance de chaîne approximative entre les vecteurs de caractères. La distance est une distance généralisée de Levenshtein (édition), donnant le nombre minimal éventuellement pondéré d'insertions, de suppressions et de substitutions nécessaires pour transformer une chaîne en une autre.

La fonction stringdistd'un package du même nom a plusieurs méthodes (voir ?stringdist):

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

Avec cela, vous pouvez sélectionner une divergence maximale (seuil):

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

Cette trame de données montre le premier vecteur dans la colonne premier vecteur, la meilleure correspondance du deuxième vecteur dans la correspondance de colonne, sa distance dans la divergence de colonne et toutes les correspondances significatives ordonnées dans les correspondances de colonnes triées comme dans le PO.

Ferroao
la source
2
Bien que la mise en œuvre soit souvent associée à des questions de fond, nous sommes censés être un site fournissant des informations sur les statistiques, l'apprentissage automatique, etc., et non le code. Il peut être bon de fournir du code également, mais veuillez élaborer votre réponse de fond dans le texte pour les personnes qui ne lisent pas suffisamment cette langue pour reconnaître et extraire la réponse du code.
gung - Rétablir Monica