web-dev-qa-db-fra.com

est-il possible de fusionner des correspondances floues avec des pandas de python?

J'ai deux DataFrames que je veux fusionner basé sur une colonne. Toutefois, en raison d’orthographes alternatifs, du nombre différent d’espaces, de l’absence ou de la présence de signes diacritiques, je souhaiterais pouvoir fusionner, à condition qu’ils soient similaires.

N'importe quel algorithme de similarité fera l'affaire (soundex, levenshtein, difflib's). 

Disons qu'un DataFrame a les données suivantes:

df1 = DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])

       number
one         1
two         2
three       3
four        4
five        5

df2 = DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])

      letter
one        a
too        b
three      c
fours      d
five       e

Ensuite, je veux obtenir le DataFrame résultant

       number letter
one         1      a
two         2      b
three       3      c
four        4      d
five        5      e
42
pocketfullofcheese

Semblable à la suggestion @locojay, vous pouvez appliquer get_close_matches de difflib à l'index de df2, puis appliquer une variable join :

In [23]: import difflib 

In [24]: difflib.get_close_matches
Out[24]: <function difflib.get_close_matches>

In [25]: df2.index = df2.index.map(lambda x: difflib.get_close_matches(x, df1.index)[0])

In [26]: df2
Out[26]: 
      letter
one        a
two        b
three      c
four       d
five       e

In [31]: df1.join(df2)
Out[31]: 
       number letter
one         1      a
two         2      b
three       3      c
four        4      d
five        5      e

.

Si c'étaient des colonnes, dans le même esprit, vous pourriez appliquer à la colonne alors merge :

df1 = DataFrame([[1,'one'],[2,'two'],[3,'three'],[4,'four'],[5,'five']], columns=['number', 'name'])
df2 = DataFrame([['a','one'],['b','too'],['c','three'],['d','fours'],['e','five']], columns=['letter', 'name'])

df2['name'] = df2['name'].apply(lambda x: difflib.get_close_matches(x, df1['name'])[0])
df1.merge(df2)
58
Andy Hayden

J'ai écrit un paquet Python qui vise à résoudre ce problème:

pip install fuzzymatcher

Vous pouvez trouver le repo ici et docs ici .

Utilisation de base:

Étant donné que vous souhaitez associer de façon fuzzy deux images df_left et df_right, vous pouvez écrire ce qui suit:

from fuzzymatcher import link_table, left join

# Columns to match on from df_left
left_on = ["fname", "mname", "lname",  "dob"]

# Columns to match on from df_right
right_on = ["name", "middlename", "surname", "date"]

# The link table potentially contains several matches for each record
fuzzymatcher.link_table(df_left, df_right, left_on, right_on)

Ou si vous souhaitez simplement créer un lien vers la correspondance la plus proche:

fuzzymatcher.fuzzy_left_join(df_left, df_right, left_on, right_on)
10
RobinL

J'utiliserais Jaro-Winkler, car c'est l'un des algorithmes d'appariement de chaînes approximatifs les plus performants et les plus précis actuellement disponibles [ Cohen, et al. ], [ Winkler ].

Voici comment je le ferais avec Jaro-Winkler du package jellyfish :

def get_closest_match(x, list_strings):

  best_match = None
  highest_jw = 0

  for current_string in list_strings:
    current_score = jellyfish.jaro_winkler(x, current_string)

    if(current_score > highest_jw):
      highest_jw = current_score
      best_match = current_string

  return best_match

df1 = pandas.DataFrame([[1],[2],[3],[4],[5]], index=['one','two','three','four','five'], columns=['number'])
df2 = pandas.DataFrame([['a'],['b'],['c'],['d'],['e']], index=['one','too','three','fours','five'], columns=['letter'])

df2.index = df2.index.map(lambda x: get_closest_match(x, df1.index))

df1.join(df2)

Sortie:

    number  letter
one     1   a
two     2   b
three   3   c
four    4   d
five    5   e
8
lostsoul29

http://pandas.pydata.org/pandas-docs/dev/merging.html n'a pas de fonction de raccordement pour le faire à la volée. Serait bien si ...

Je voudrais juste faire une étape séparée et utiliser difflib getclosest_matches pour créer une nouvelle colonne dans l'une des 2 images de données et la fusion/jointure sur la colonne correspondante floue 

5
locojay

En tête-à-tête, cela fonctionne, sauf si aucune correspondance n'est trouvée, ou si vous avez des NaN dans l'une des colonnes. Au lieu d'appliquer directement get_close_matches, j'ai trouvé plus facile d'appliquer la fonction suivante. Le choix des substituts NaN dépendra beaucoup de votre jeu de données. 

def fuzzy_match(a, b):
    left = '1' if pd.isnull(a) else a
    right = b.fillna('2')
    out = difflib.get_close_matches(left, right)
    return out[0] if out else np.NaN
2
Luke

Vous pouvez utiliser d6tjoin pour cela

import d6tjoin.top1
d6tjoin.top1.MergeTop1(df1.reset_index(),df2.reset_index(),
       fuzzy_left_on=['index'],fuzzy_right_on=['index']).merge()['merged']

index number index_right letter 0 one 1 one a 1 two 2 too b 2 three 3 three c 3 four 4 fours d 4 five 5 five e

Il possède une variété de fonctionnalités supplémentaires telles que:

  • vérifier la qualité de la jointure, avant et après la jointure
  • personnaliser la fonction de similarité, par exemple modifier la distance en fonction de la distance de Hamming
  • spécifier la distance maximale
  • calcul multi-core

Pour plus de détails voir

0
citynorman