Как сравнить строку за строкой в ​​кадре данных - PullRequest
1 голос
/ 15 февраля 2020

У меня есть фрейм данных, который имеет имя и URL-адрес имени. Например:

Abc           123
Abc.com       123
Def           345
Pqr           123
PQR.com       123

Здесь из-за ошибки извлечения данных, иногда разные имена имеют одинаковый идентификатор. Я хочу очистить таблицу так, чтобы, если имена разные и ID был одинаковым, то запись должна оставаться прежней. Если имена похожи, а ID тоже совпадает, имя следует изменить на единицу. Чтобы быть понятным,

Ожидаемый результат должен быть:

Abc.com     123
Abc.com     123
Def         354
PQR.com     123
PQR.com     123

То есть, последний был ошибкой ввода данных .. и оба были одинаковыми (первое слово строки было так же). Таким образом, они меняются на одно имя, глядя на ID. Но первая и вторая записи, несмотря на то, что они имели идентичный идентификатор с последними, их имена не совпадали и были совершенно разными.

Я не могу понять, как этого добиться.

Запросите некоторые указания здесь. Заранее спасибо.

Примечание. Размер набора данных составляет почти 16 миллионов таких записей.

1 Ответ

2 голосов
/ 15 февраля 2020

Идея заключается в использовании нечеткого сопоставления lib fuzzywuzzy для ratio всех комбинаций Name s путем перекрестного соединения на DataFrame.merge и удаления строк с одинаковыми именами в обоих столбцах на DataFrame.query, также добавлен новый столбец по длинам данных на Series.str.len:

from fuzzywuzzy import fuzz

df1 = df.merge(df, on='ID').query('Name_x != Name_y')
df1['ratio'] = df1.apply(lambda x:  fuzz.ratio(x['Name_x'], x['Name_y']), axis=1)
df1['len'] = df1['Name_x'].str.len()
print (df1)
    Name_x   ID   Name_y  ratio  len
1      Abc  123      BCD      0    3
2      BCD  123      Abc      0    3
6      Pqr  789  PQR.com     20    3
7  PQR.com  789      Pqr     20    7

Затем отфильтруйте строки по порогу и boolean indexing. Затем необходимо выбрать, какое значение необходимо, одно из возможных решений - получить более длинный текст. Так что использует DataFrameGroupBy.idxmax с DataFrame.loc, а затем DataFrame.set_index для Series:

N = 15    
df2 = df1[df1['ratio'].gt(N)]
s = df2.loc[df2.groupby('ID')['len'].idxmax()].set_index('ID')['Name_x']
print (s)
ID
789    PQR.com
Name: Name_x, dtype: object

Last Series.map на ID и замените несовпадающие значения на оригинальные на Series.fillna:

df['Name'] = df['ID'].map(s).fillna(df['Name'])
print (df)
      Name   ID
0      Abc  123
1      BCD  123
2      Def  345
3  PQR.com  789
4  PQR.com  789

РЕДАКТИРОВАТЬ: если в ID имеется больше допустимых строк это сложнее:

print (df)
               Name          ID
0      Air Ordnance  1578013421
1  Air-Ordnance.com  1578013421
2          Garreett  1578013421
3           Garrett  1578013421

Сначала получите fuzz.ratio, как в решении до:

from fuzzywuzzy import fuzz

df1 = df.merge(df, on='ID').query('Name_x != Name_y')
df1['ratio'] = df1.apply(lambda x:  fuzz.ratio(x['Name_x'], x['Name_y']), axis=1)
print (df1)
              Name_x          ID            Name_y  ratio
1       Air Ordnance  1578013421  Air-Ordnance.com     79
2       Air Ordnance  1578013421          Garreett     30
3       Air Ordnance  1578013421           Garrett     32
4   Air-Ordnance.com  1578013421      Air Ordnance     79
6   Air-Ordnance.com  1578013421          Garreett     25
7   Air-Ordnance.com  1578013421           Garrett     26
8           Garreett  1578013421      Air Ordnance     30
9           Garreett  1578013421  Air-Ordnance.com     25
11          Garreett  1578013421           Garrett     93
12           Garrett  1578013421      Air Ordnance     32
13           Garrett  1578013421  Air-Ordnance.com     26
14           Garrett  1578013421          Garreett     93

Затем отфильтруйте по порогу:

N = 50    
df2 = df1[df1['ratio'].gt(N)]
print (df2)

              Name_x          ID            Name_y  ratio
1       Air Ordnance  1578013421  Air-Ordnance.com     79
4   Air-Ordnance.com  1578013421      Air Ordnance     79
11          Garreett  1578013421           Garrett     93
14           Garrett  1578013421          Garreett     93

Но для большего необходима точность, укажите, какие строки действительны в списке L, отфильтруйте по списку:

L = ['Air-Ordnance.com','Garrett']
df2 = df2.loc[df2['Name_x'].isin(L),['Name_x','Name_y','ID']].rename(columns={'Name_y':'Name'})
print (df2)
              Name_x          Name          ID
4   Air-Ordnance.com  Air Ordnance  1578013421
14           Garrett      Garreett  1578013421

Last merge с левым соединением к исходному и отмените пропущенные значения:

df = df.merge(df2, on=['Name','ID'], how='left')
df['Name'] = df.pop('Name_x').fillna(df['Name'])
print (df)
               Name          ID
0  Air-Ordnance.com  1578013421
1  Air-Ordnance.com  1578013421
2           Garrett  1578013421
3           Garrett  1578013421
...