¿Es posible hacer combinaciones de combinaciones difusas con pandas python?

Tengo dos DataFrames que quiero fusionar en base a una columna. Sin embargo, debido a la ortografía alternativa, diferente número de espacios, ausencia / presencia de marcas diacríticas, me gustaría poder fusionar siempre que sean similares entre sí.

Cualquier algoritmo de similitud servirá (soundex, Levenshtein, difflib’s).

Digamos que un DataFrame tiene los siguientes datos:

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 

Entonces quiero obtener el DataFrame resultante

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

De manera similar a la sugerencia de @locojay, puede aplicar difflib de get_close_matches al get_close_matches de df2 y luego aplicar una join :

 In [23]: import difflib In [24]: difflib.get_close_matches Out[24]:  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 se tratara de columnas, en el mismo sentido podría aplicar a la columna y luego 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) 

He escrito un paquete de Python que tiene como objective resolver este problema:

pip install fuzzymatcher

Puedes encontrar el repository aquí y los documentos aquí .

Uso básico:

Dados dos marcos de datos df_left y df_right , a los que desea unir de forma difusa, puede escribir lo siguiente:

 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) 

O si solo quieres unir en la coincidencia más cercana:

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

Yo usaría Jaro-Winkler, porque es uno de los algoritmos de emparejamiento de cadenas aproximadas más eficaces y precisos actualmente disponibles [ Cohen, et al. ], [ Winkler ].

Así es como lo haría con Jaro-Winkler del paquete de medusas :

 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) 

Salida:

  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 no tiene una función de enganche para hacer esto sobre la marcha. Estaría bien aunque …

Simplemente haría un paso por separado y usaría difflib getclosest_matches para crear una nueva columna en uno de los 2 marcos de datos y la combinación / combinación en la columna de coincidencia difusa

Como una advertencia, esto básicamente funciona, excepto si no se encuentra una coincidencia, o si tiene NaN en cualquiera de las columnas. En lugar de aplicar directamente get_close_matches , me resultó más fácil aplicar la siguiente función. La elección de los reemplazos de NaN dependerá mucho de su conjunto de datos.

 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 

Puedes usar d6tjoin para eso

 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

Tiene una variedad de características adicionales tales como:

  • Compruebe la calidad de la unión, antes y después de unirse
  • Personalice la función de similitud, por ejemplo, la distancia de edición frente a la distancia de Hamming.
  • especifique la distancia máxima
  • cálculo multi-core

Para más detalles ver

  • Ejemplos MergeTop1 – Cuaderno de ejemplos de mejor combinación
  • Ejemplos de prejuego : ejemplos para diagnosticar problemas de unión