programing

파이썬 팬더와 퍼지 매치 병합을 할 수 있습니까?

powerit 2023. 9. 5. 20:49
반응형

파이썬 팬더와 퍼지 매치 병합을 할 수 있습니까?

열을 기준으로 병합하려는 두 개의 데이터 프레임이 있습니다.그러나 대체 철자법, 다른 공백 수, 분음 부호의 부재/존재로 인해 서로 유사한 한 병합할 수 있기를 원합니다.

모든 유사성 알고리즘(soundex, Levenshtein, difflib's)이 가능합니다.

한 DataFrame에 다음과 같은 데이터가 있다고 가정합니다.

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

그런 다음 결과 데이터 프레임을 가져옵니다.

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

@locojay 제안과 유사하게 에 를 적용할 수 있습니다.df2의 인덱스를 적용합니다.

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

.

이들이 열인 경우 동일한 맥락에서 열에 적용할 수 있습니다.

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)

용사를 합니다.fuzzywuzzy

예가 없기 때문에fuzzywuzzy패키지, 사용자로 설정할 수 있는 임계값을 기준으로 모든 일치 항목을 반환하는 기능은 다음과 같습니다.


예제 datframe

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})
df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

# df1
          Key
0       Apple
1      Banana
2      Orange
3  Strawberry

# df2
        Key
0      Aple
1     Mango
2      Orag
3     Straw
4  Bannanna
5     Berry

퍼지 매칭을 위한 함수

def fuzzy_merge(df_1, df_2, key1, key2, threshold=90, limit=2):
    """
    :param df_1: the left table to join
    :param df_2: the right table to join
    :param key1: key column of the left table
    :param key2: key column of the right table
    :param threshold: how close the matches should be to return a match, based on Levenshtein distance
    :param limit: the amount of matches that will get returned, these are sorted high to low
    :return: dataframe with boths keys and matches
    """
    s = df_2[key2].tolist()
    
    m = df_1[key1].apply(lambda x: process.extract(x, s, limit=limit))    
    df_1['matches'] = m
    
    m2 = df_1['matches'].apply(lambda x: ', '.join([i[0] for i in x if i[1] >= threshold]))
    df_1['matches'] = m2
    
    return df_1

데이터 프레임에서 NAT 기능 사용: #1

from fuzzywuzzy import fuzz
from fuzzywuzzy import process

fuzzy_merge(df1, df2, 'Key', 'Key', threshold=80)

          Key       matches
0       Apple          Aple
1      Banana      Bannanna
2      Orange          Orag
3  Strawberry  Straw, Berry

데이터 프레임에서 NAT 기능 사용: #2

df1 = pd.DataFrame({'Col1':['Microsoft', 'Google', 'Amazon', 'IBM']})
df2 = pd.DataFrame({'Col2':['Mcrsoft', 'gogle', 'Amason', 'BIM']})

fuzzy_merge(df1, df2, 'Col1', 'Col2', 80)

        Col1  matches
0  Microsoft  Mcrsoft
1     Google    gogle
2     Amazon   Amason
3        IBM         

설치:

pip install fuzzywuzzy

아나콘다

conda install -c conda-forge fuzzywuzzy

저는 이 문제를 해결하기 위한 Python 패키지를 작성했습니다.

pip install fuzzymatcher

여기에서 레포와 문서를 찾을 수 있습니다.

기본 사용:

개의 프레임이 주어졌을 때df_left그리고.df_right퍼지 가입을 원하는 경우 다음과 같이 작성할 수 있습니다.

from fuzzymatcher import link_table, fuzzy_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)

또는 가장 가까운 일치 항목에 연결하려는 경우:

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

저는 Jaro-Winkler를 사용할 것입니다. 왜냐하면 그것은 현재 사용 가능한 가장 성능이 뛰어나고 정확한 근사 문자열 매칭 알고리즘 중 하나이기 때문입니다 [Cohen, et al.], [Winkler].

젤리피쉬 패키지의 Jaro-Winkler와 함께 하는 방법은 다음과 같습니다.

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)

출력:

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

일적인접방경우의식근반:fuzzy_merge

약간 다른 문자열을 포함하는 두 개의 데이터 프레임에서 열을 병합하려는 보다 일반적인 시나리오의 경우, 다음 함수는 판다의 기능을 모방하기 위해 함께 사용합니다.merge퍼지 매칭을 사용하는 경우:

import difflib 

def fuzzy_merge(df1, df2, left_on, right_on, how='inner', cutoff=0.6):
    df_other= df2.copy()
    df_other[left_on] = [get_closest_match(x, df1[left_on], cutoff) 
                         for x in df_other[right_on]]
    return df1.merge(df_other, on=left_on, how=how)

def get_closest_match(x, other, cutoff):
    matches = difflib.get_close_matches(x, other, cutoff=cutoff)
    return matches[0] if matches else None

다음은 두 개의 샘플 데이터 프레임에 대한 몇 가지 사용 사례입니다.

print(df1)

     key   number
0    one       1
1    two       2
2  three       3
3   four       4
4   five       5

print(df2)

                 key_close  letter
0                    three      c
1                      one      a
2                      too      b
3                    fours      d
4  a very different string      e

위의 예를 통해 다음과 같은 이점을 얻을 수 있습니다.

fuzzy_merge(df1, df2, left_on='key', right_on='key_close')

     key  number key_close letter
0    one       1       one      a
1    two       2       too      b
2  three       3     three      c
3   four       4     fours      d

왼쪽 조인은 다음과 같이 수행할 수 있습니다.

fuzzy_merge(df1, df2, left_on='key', right_on='key_close', how='left')

     key  number key_close letter
0    one       1       one      a
1    two       2       too      b
2  three       3     three      c
3   four       4     fours      d
4   five       5       NaN    NaN

오른쪽 조인의 경우 왼쪽 데이터 프레임에 일치하지 않는 모든 키가 있습니다.None:

fuzzy_merge(df1, df2, left_on='key', right_on='key_close', how='right')

     key  number                key_close letter
0    one     1.0                      one      a
1    two     2.0                      too      b
2  three     3.0                    three      c
3   four     4.0                    fours      d
4   None     NaN  a very different string      e

또한 컷오프 내에서 일치하는 항목이 없는 경우 빈 목록을 반환합니다.공유 예제에서 마지막 인덱스를 변경하는 경우df2말하는 사람:

print(df2)

                          letter
one                          a
too                          b
three                        c
fours                        d
a very different string      e

우리는 받을 수 있습니다.index out of range 오류: 오류:

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

IndexError: 목록 인덱스가 범위를 벗어남

의 함수는 이해기위위기능은의해입니다.get_closest_match 가장 항목을 합니다.difflib.get_close_matches 실제로 일치하는 항목이 포함된 경우에만 해당됩니다.

http://pandas.pydata.org/pandas-docs/dev/merging.html 에는 이를 즉시 수행할 수 있는 후크 기능이 없습니다.그래도 좋겠죠...

나는 단지 별도의 단계를 수행하고 difflib getclose_matches를 사용하여 두 개의 데이터 프레임 중 하나에 새 열을 만들고 퍼지 일치 열에 병합/결합할 것입니다.

나는 퍼지매처 패키지를 사용했고 이것은 나에게 잘 작동했습니다.자세한 내용은 이 링크를 참조하십시오.

다음 명령을 사용하여 설치합니다.

pip install fuzzymatcher

아래는 샘플 코드입니다(위의 RobinL이 이미 제출).)

from fuzzymatcher import link_table, fuzzy_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)

발생할 수 있는 오류

  1. ZeroDivisionError: 0에 의한 floatdivision---> 이 링크를 참조하여 해결하십시오.
  2. 작동 오류: No Such Module:fts4 --> 여기서 sqlite3.dll을 다운로드하고 python 또는 anaconda DLL 폴더의 DLL 파일을 바꿉니다.

찬성:

  1. 더 빨리 작동합니다.의 경우, 저는 3,000개의 행이 있는 하나의 데이터 프레임과 170,000개의 레코드가 있는 하나의 데이터 프레임을 비교했습니다.또한 여러 텍스트에 걸쳐 SQLite3 검색을 사용합니다.많은 사람들보다 훨씬 더 빠릅니다.
  2. 여러 열과 두 개의 데이터 프레임에서 확인할 수 있습니다.저의 경우, 주소와 회사 이름을 기준으로 가장 가까운 일치 항목을 찾고 있었습니다. 가끔 회사 이름이 같을 수도 있지만 주소도 확인해보면 좋습니다.
  3. 동일한 기록에 대해 가장 근접한 모든 경기에 대한 점수를 제공합니다.당신은 컷오프 점수를 선택합니다.

단점:

  1. 원래 패키지 설치가 버그입니다.
  2. 필요한 C++ 및 비주얼 스튜디오도 설치됨
  3. 64비트 아나콘다/파이썬에서는 작동하지 않습니다.

가 있습니다.fuzzy_pandas 사용할 수 levenshtein,jaro,metaphone그리고.bilenco방법들.여기 몇 가지 훌륭한 를 들어 보겠습니다.

import pandas as pd
import fuzzy_pandas as fpd

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})
df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

results = fpd.fuzzy_merge(df1, df2,
            left_on='Key',
            right_on='Key',
            method='levenshtein',
            threshold=0.6)

results.head()

  Key    Key
0 Apple  Aple
1 Banana Bannanna
2 Orange Orag

용사를 합니다.thefuzz

패키지인 시트지크를 하는 것.thefuzz레벤쉬테인 거리를 이용한 것입니다.이것은 열에 보관된 데이터에서 작동합니다.일치 항목을 열이 아닌 행으로 추가하여 정돈된 데이터 세트를 보존하고 추가 열을 출력 데이터 프레임으로 쉽게 풀링할 수 있습니다.


샘플 데이터

df1 = pd.DataFrame({'col_a':['one','two','three','four','five'], 'col_b':[1, 2, 3, 4, 5]})

    col_a   col_b
0   one     1
1   two     2
2   three   3
3   four    4
4   five    5

df2 = pd.DataFrame({'col_a':['one','too','three','fours','five'], 'col_b':['a','b','c','d','e']})

    col_a   col_b
0   one     a
1   too     b
2   three   c
3   fours   d
4   five    e

일치를 수행하는 데 사용

def fuzzy_match(
    df_left, df_right, column_left, column_right, threshold=90, limit=1
):
    # Create a series
    series_matches = df_left[column_left].apply(
        lambda x: process.extract(x, df_right[column_right], limit=limit)            # Creates a series with id from df_left and column name _column_left_, with _limit_ matches per item
    )

    # Convert matches to a tidy dataframe
    df_matches = series_matches.to_frame()
    df_matches = df_matches.explode(column_left)     # Convert list of matches to rows
    df_matches[
        ['match_string', 'match_score', 'df_right_id']
    ] = pd.DataFrame(df_matches[column_left].tolist(), index=df_matches.index)       # Convert match tuple to columns
    df_matches.drop(column_left, axis=1, inplace=True)      # Drop column of match tuples

    # Reset index, as in creating a tidy dataframe we've introduced multiple rows per id, so that no longer functions well as the index
    if df_matches.index.name:
        index_name = df_matches.index.name     # Stash index name
    else:
        index_name = 'index'        # Default used by pandas
    df_matches.reset_index(inplace=True)
    df_matches.rename(columns={index_name: 'df_left_id'}, inplace=True)       # The previous index has now become a column: rename for ease of reference

    # Drop matches below threshold
    df_matches.drop(
        df_matches.loc[df_matches['match_score'] < threshold].index,
        inplace=True
    )

    return df_matches

함수 사용 및 데이터 병합

import pandas as pd
from thefuzz import process

df_matches = fuzzy_match(
    df1,
    df2,
    'col_a',
    'col_a',
    threshold=60,
    limit=1
)

df_output = df1.merge(
    df_matches,
    how='left',
    left_index=True,
    right_on='df_left_id'
).merge(
    df2,
    how='left',
    left_on='df_right_id',
    right_index=True,
    suffixes=['_df1', '_df2']
)

df_output.set_index('df_left_id', inplace=True)       # For some reason the first merge operation wrecks the dataframe's index. Recreated from the value we have in the matches lookup table

df_output = df_output[['col_a_df1', 'col_b_df1', 'col_b_df2']]      # Drop columns used in the matching
df_output.index.name = 'id'

id  col_a_df1   col_b_df1   col_b_df2
0   one         1           a
1   two         2           b
2   three       3           c
3   four        4           d
4   five        5           e

: 다음을 사용한 퍼지 매칭thefuzz선택적으로 설치하는 경우 훨씬 빠릅니다.python-Levenshtein포장도.

이는 일치하는 항목이 없거나 두 열에 NaN이 있는 경우를 제외하고 기본적으로 작동합니다.직접 적용하는 대신get_close_matches저는 다음 기능을 적용하는 것이 더 쉽다는 것을 알았습니다.NaN 대체품의 선택은 데이터 세트에 따라 크게 달라집니다.

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

당신은 그것을 위해 d6tjoin을 사용할 수 있습니다.

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

다음과 같은 다양한 추가 기능이 있습니다.

  • 접합 품질, 접합 전후 확인
  • 유사성 함수 사용자 정의(예: 거리 대 해밍 거리)
  • 최대 거리 지정
  • 멀티 코어 컴퓨팅

자세한 내용은 다음을 참조하십시오.

  • 상위 1개 병합 예제 - 최상의 일치 조인 예제 노트북
  • 조인 전 예 - 조인 문제 진단을 위한 예

사용한 적이 있습니다.fuzzywuzz의 기존 행동과 키워드를 일치시키면서 매우 최소한의 방법으로.merge안에pandas.

수락 여부를 지정하십시오.threshold(사이의) 매칭을 위하여0그리고.100):

from fuzzywuzzy import process

def fuzzy_merge(df, df2, on=None, left_on=None, right_on=None, how='inner', threshold=80):
    
    def fuzzy_apply(x, df, column, threshold=threshold):
        if type(x)!=str:
            return None
        
        match, score, *_ = process.extract(x, df[column], limit=1)[0]
            
        if score >= threshold:
            return match

        else:
            return None
    
    if on is not None:
        left_on = on
        right_on = on

    # create temp column as the best fuzzy match (or None!)
    df2['tmp'] = df2[right_on].apply(
        fuzzy_apply, 
        df=df, 
        column=left_on, 
        threshold=threshold
    )

    merged_df = df.merge(df2, how=how, left_on=left_on, right_on='tmp')
    
    del merged_df['tmp']
    
    return merged_df

예제 데이터를 사용하여 사용해 보십시오.

df1 = pd.DataFrame({'Key':['Apple', 'Banana', 'Orange', 'Strawberry']})

df2 = pd.DataFrame({'Key':['Aple', 'Mango', 'Orag', 'Straw', 'Bannanna', 'Berry']})

fuzzy_merge(df, df2, on='Key', threshold=80)

사용할 수 있는 열이 많은 행과 일치하는 더 복잡한 사용 사례recordlinkage꾸러미recordlinkage다음 사이의 행을 퍼지 일치시키는 모든 도구를 제공합니다.pandas통합 시 데이터 중복 제거에 도움이 되는 데이터 프레임.저는 여기에 소포에 대한 자세한 기사를 썼습니다.

조인 축이 숫자인 경우 지정된 공차의 인덱스를 일치시키는 데도 사용할 수 있습니다.

def fuzzy_left_join(df1, df2, tol=None):
    index1 = df1.index.values
    index2 = df2.index.values

    diff = np.abs(index1.reshape((-1, 1)) - index2)
    mask_j = np.argmin(diff, axis=1)  # min. of each column
    mask_i = np.arange(mask_j.shape[0])

    df1_ = df1.iloc[mask_i]
    df2_ = df2.iloc[mask_j]

    if tol is not None:
        mask = np.abs(df2_.index.values - df1_.index.values) <= tol
        df1_ = df1_.loc[mask]
        df2_ = df2_.loc[mask]

    df2_.index = df1_.index

    out = pd.concat([df1_, df2_], axis=1)
    return out

퍼지는 퍼지의 새로운 버전입니다.

두 개의 큰 테이블에서 문자열 요소를 퍼지 결합하려면 다음을 수행합니다.

  1. 적용을 사용하여 한 행씩 이동
  2. 더 빠른 속도로 병렬 처리, 속도 향상 및 기본 적용 기능 시각화(색상 진행 표시줄 포함)
  3. 컬렉션에서 OrderedDict를 사용하여 병합 출력에서 중복 항목을 제거하고 초기 순서를 유지합니다.
  4. 제한 증가:thefuzz.process.extract병합에 대한 추가 옵션 보기(유사도가 %인 튜플 목록에 포함됨)

사용할 수 있습니다.thefuzz.process.extractOnethefuzz.process.extract제한을 지정하지 않고 가장 적합한 항목 하나만 반환합니다.그러나 여러 결과의 유사성이 동일한 %일 수 있으며 그 중 하나만 얻을 수 있습니다.

어떻게든 스위퍼는 실제 적용을 시작하기 전에 1~2분 정도 걸립니다.작은 테이블을 처리해야 하는 경우 이 단계를 건너뛰고 progress_apply를 대신 사용할 수 있습니다.

from thefuzz import process
from collections import OrderedDict
import swifter    


def match(x):
    matches = process.extract(x, df1, limit=6)
    matches = list(OrderedDict((x, True) for x in matches).keys())
    print(f'{x:20} : {matches}')

    return str(matches)


df1 = df['name'].values
df2['matches'] = df2['name'].swifter.apply(lambda x: match(x))

저는 이것이 매우 효율적이라는 것을 알았습니다.기능에 대한 자세한 설명:

from fuzzywuzzy.process import extract

def efficient_matching(df1,
                       col1,
                       df2,
                       col2,
                       limit=3,
                       length_diff=3,
                       first_letter_match=2
                       ):
    """
    For each name that we want to find matches for, it's more efficient to only look at a subset of potential matches. 
    One way to narrow down all the matches to potential matches is length. Here are 2 methods:
    1. If the name is "Markos", we don't need to check how similar markos is to names with length less than 4 or 
    more than 8. This window is determined by length_diff.
    2. We consider names from the corpus whose first 2 letters are similar the first letters of the name we want to find
    the match for.

    limit: Gives how many closest matches to return.
    """
    df1[col1] = df1[col1].astype(str)
    df2[col2] = df2[col2].astype(str)

    df1['_len_'] = df1[col1].apply(len)
    df2['_len_'] = df2[col2].apply(len)
    df2 = df2[df2['_len_'] >= 2]

    matches = df1[[col1, '_len_']].apply(lambda x: 
                                        extract(x[0], 
                                                df2[
                                                    ((df2['_len_'] - x[1]).abs() < length_diff) &
                                                    (df2[col2].str[:first_letter_match]==x[0][:first_letter_match])
                                                    ][col2].tolist(), 
                                                limit = limit
                                                ), 
                                        axis=1
                                        ) 
    return matches

언급URL : https://stackoverflow.com/questions/13636848/is-it-possible-to-do-fuzzy-match-merge-with-python-pandas

반응형