파이썬 팬더와 퍼지 매치 병합을 할 수 있습니까?
열을 기준으로 병합하려는 두 개의 데이터 프레임이 있습니다.그러나 대체 철자법, 다른 공백 수, 분음 부호의 부재/존재로 인해 서로 유사한 한 병합할 수 있기를 원합니다.
모든 유사성 알고리즘(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)
발생할 수 있는 오류
- ZeroDivisionError: 0에 의한 floatdivision---> 이 링크를 참조하여 해결하십시오.
- 작동 오류: No Such Module:fts4 --> 여기서 sqlite3.dll을 다운로드하고 python 또는 anaconda DLL 폴더의 DLL 파일을 바꿉니다.
찬성:
- 더 빨리 작동합니다.저의 경우, 저는 3,000개의 행이 있는 하나의 데이터 프레임과 170,000개의 레코드가 있는 하나의 데이터 프레임을 비교했습니다.또한 여러 텍스트에 걸쳐 SQLite3 검색을 사용합니다.많은 사람들보다 훨씬 더 빠릅니다.
- 여러 열과 두 개의 데이터 프레임에서 확인할 수 있습니다.저의 경우, 주소와 회사 이름을 기준으로 가장 가까운 일치 항목을 찾고 있었습니다. 가끔 회사 이름이 같을 수도 있지만 주소도 확인해보면 좋습니다.
- 동일한 기록에 대해 가장 근접한 모든 경기에 대한 점수를 제공합니다.당신은 컷오프 점수를 선택합니다.
단점:
- 원래 패키지 설치가 버그입니다.
- 필요한 C++ 및 비주얼 스튜디오도 설치됨
- 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
다음과 같은 다양한 추가 기능이 있습니다.
- 접합 품질, 접합 전후 확인
- 유사성 함수 사용자 정의(예: 거리 대 해밍 거리)
- 최대 거리 지정
- 멀티 코어 컴퓨팅
자세한 내용은 다음을 참조하십시오.
사용한 적이 있습니다.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
퍼지는 퍼지의 새로운 버전입니다.
두 개의 큰 테이블에서 문자열 요소를 퍼지 결합하려면 다음을 수행합니다.
- 적용을 사용하여 한 행씩 이동
- 더 빠른 속도로 병렬 처리, 속도 향상 및 기본 적용 기능 시각화(색상 진행 표시줄 포함)
- 컬렉션에서 OrderedDict를 사용하여 병합 출력에서 중복 항목을 제거하고 초기 순서를 유지합니다.
- 제한 증가:
thefuzz.process.extract
병합에 대한 추가 옵션 보기(유사도가 %인 튜플 목록에 포함됨)
사용할 수 있습니다.thefuzz.process.extractOne
에 thefuzz.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
'programing' 카테고리의 다른 글
View Pager에서 조각 검색 (0) | 2023.09.05 |
---|---|
Windows 서버에서 MySQL 데이터베이스 자동 백업 (0) | 2023.09.05 |
CSS3 선택기: 클래스 이름을 가진 첫 번째 유형? (0) | 2023.09.05 |
mariadb 10.3의 해당 mysql 버전 (0) | 2023.09.05 |
Larvel 5.4 필드에 기본값이 없습니다. (0) | 2023.09.05 |