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
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)
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)
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
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
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
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:
Pour plus de détails voir