Панды: приблизительное соединение в одном столбце, точное совпадение в других столбцах

У меня есть два кадра данных pandas, которые я хочу соединить/объединить точно по количеству столбцов (скажем, 3) и приблизительно, то есть по ближайшему соседу, по одному столбцу (дата). Я также хочу вернуть разницу (дни) между ними. Каждый набор данных содержит около 50 000 строк. Меня больше всего интересует внутреннее соединение, но «остатки» также интересны, хотя и не слишком сложны для получения. Большинство наблюдений «точного совпадения» будут существовать несколько раз в каждом фрейме данных.

Я пытался использовать difflib.get_close_matches для объединения всех из них в виде строк (что глупо, я знаю!), но это не всегда дает точные совпадения. Я полагаю, мне нужно сначала перебрать точные совпадения, а затем найти ближайшие совпадения в этой группе, но я просто не могу понять это правильно...

Кадры данных выглядят примерно так:

df1 = pd.DataFrame({'index': ['a1','a2','a3','a4'], 'col1': ['1232','432','432','123'], 'col2': ['asd','dsa12','dsa12','asd2'], 'col3': ['1','2','2','3'], 'date': ['2010-01-23','2016-05-20','2010-06-20','2008-10-21'],}).set_index('index')

df1
Out[430]: 
       col1   col2 col3        date
index                              
a1     1232    asd    1  2010-01-23
a2      432  dsa12    2  2016-05-20
a3      432  dsa12    2  2010-06-20
a4      123   asd2    3  2008-10-21

df2 = pd.DataFrame({'index': ['b1','b2','b3','b4'], 'col1': ['132','432','432','123'], 'col2': ['asd','dsa12','dsa12','sd2'], 'col3': ['1','2','2','3'], 'date': ['2010-01-23','2016-05-23','2010-06-10','2008-10-21'],}).set_index('index')

df2
Out[434]: 
      col1   col2 col3        date
index                             
b1     132    asd    1  2010-01-23
b2     432  dsa12    2  2016-05-23
b3     432  dsa12    2  2010-06-10
b4     123    sd2    3  2008-10-21

В конце концов, я хочу что-то вроде:

       col1   col2 col3        date diff match_index
index                              
a1     1232    asd    1  2010-01-23  nan         nan
a2      432  dsa12    2  2016-05-20   -3          b2
a3      432  dsa12    2  2010-06-20   10          b3
a4      123   asd2    3  2008-10-21  nan         nan
a5      123    sd2    3  2008-10-21  nan          b4

или, если это проще, только с внутренним соединением, я бы хотел:

       col1   col2 col3        date diff match_index
index                                                     
a2      432  dsa12    2  2016-05-20   -3          b2
a3      432  dsa12    2  2010-06-20   10          b3

person CAR ERL    schedule 31.05.2016    source источник
comment
Возможно, имеет смысл задавать отдельные вопросы для каждой из вещей, которые вы ищете.   -  person fmarc    schedule 31.05.2016


Ответы (1)


Я не уверен, подходит ли это. Он достигает более или менее того, что вы хотите, но на самом деле не выполняет слияние. Он следует той же идее, что и этот вопрос за исключением того, что вместо подмножества df1 на основе только одного столбца здесь мы сопоставляем несколько столбцов, используя groupby, и делаем это для обоих фреймов данных. ЕСЛИ вы хотите явно включить команду merge и довольны внутренним соединением, проверьте самую нижнюю часть ответа, там есть фрагмент для этого.

import pandas as pd
from sklearn.neighbors import NearestNeighbors

    def find_nearest(group, df2, groupname):
        try:
            match = df2.groupby(groupname).get_group(group.name)
            match['date'] = pd.to_datetime(match.date, unit = 'D')
            nbrs = NearestNeighbors(1).fit(match['date'].values[:, None])
            dist, ind = nbrs.kneighbors(group['date'].values[:, None])

            group['date1'] = group['date']
            group['date'] = match['date'].values[ind.ravel()]
            group['diff'] = (group['date1']-group['date'])
            group['match_index'] = match.index[ind.ravel()]
            return group
        except KeyError:
            return group

    #change dates from string to datetime
    df1['date'] = pd.to_datetime(df1.date, unit = 'D')
    df2['date'] = pd.to_datetime(df2.date, unit = 'D')

    #find closest dates and differences
    keys = ['col1', 'col2', 'col3']
    df1_mod = df1.groupby(keys).apply(find_nearest, df2, keys)

    #fill unmatched dates 
    df1_mod.date1.fillna(df1_mod.date, inplace=True)

    df2_mod = df2.groupby(keys).apply(find_nearest, df1, keys) 
    df2_mod.date1.fillna(df2_mod.date, inplace=True)

    #drop original column 
    df1_mod.drop('date', inplace=True, axis=1)
    df1_mod.rename(columns = {'date1':'date'}, inplace=True)

    df2_mod.drop('date', inplace=True, axis=1)
    df2_mod.rename(columns = {'date1':'date'}, inplace=True)
    df2_mod['diff'] = -df2_mod['diff']

    #drop redundant values
    df2_mod.drop(df2_mod[df2_mod.match_index.str.len()>0].index, inplace=True)

    #merge the two 
    df_final = pd.merge(df1_mod, df2_mod, how='outer')

Это дает следующий результат:

In [349]: df_final
Out[349]:
   col1   col2 col3       date    diff match_index
0  1232    asd    1 2010-01-23     NaT         NaN
1   432  dsa12    2 2016-05-20 -3 days          b2
2   432  dsa12    2 2010-06-20 10 days          b3
3   123   asd2    3 2008-10-21     NaT         NaN
4   132    asd    1 2010-01-23     NaT         NaN
5   123    sd2    3 2008-10-21     NaT         NaN

С помощью команды слияния:

In [208]: pd.merge(df1_mod, df2.drop('date', axis=1), on=['col1', 'col2', 'col3']).drop_duplicates()
Out[208]:
  col1   col2 col3       date    diff match_index
0  432  dsa12    2 2016-05-20 -3 days          b2
2  432  dsa12    2 2010-06-20 10 days          b3

Случай рассмотренный в комментариях, а именно:

df1 = pd.DataFrame({'index': ['a1','a2','a3','a4'], 'col1': ['1232','1432','432','123'], 'col2': ['asd','dsa12','dsa12','asd2'], 'col3': ['1','2','2','3'], 'date': ['2010-01-23','2016-05-20','2010-06-20','2008-10-21'],}).set_index('index')

дает следующее:

In [351]: df_final
Out[351]:
   col1   col2 col3       date    diff match_index
0  1232    asd    1 2010-01-23     NaT         NaN
1  1432  dsa12    2 2016-05-20     NaT         NaN
2   432  dsa12    2 2010-06-20 10 days          b3
3   123   asd2    3 2008-10-21     NaT         NaN
4   132    asd    1 2010-01-23     NaT         NaN
5   123    sd2    3 2008-10-21     NaT         NaN
person Gene Burinsky    schedule 31.05.2016
comment
Спасибо Гена! Это почти так, но он соответствует только 1 столбцуcol3, в то время как я хочу сопоставить все col1, col2 и col3. Я попытался объединить их в один, используя df1['matchCol'] = df1['col1'].astype(str) + df1['col2'].astype(str) + df1['col3'].astype(str), но этого не произошло... кажется, это работает только тогда, когда точное совпадение является числовым значением, а не строкой? - person CAR ERL; 01.06.2016
comment
извините, это не имеет ничего общего с числовыми и строковыми... до сих пор не уверен, почему это не сработает, но дает ValueError: Found array with 0 sample(s) (shape=(0, 1)) while a minimum of 1 is required. - person CAR ERL; 01.06.2016
comment
CAR ERL, я не совсем понимаю. Если возможно, не могли бы вы предоставить фрагмент кода, который вы используете и выдает вышеупомянутое сообщение об ошибке? - person Gene Burinsky; 01.06.2016
comment
Извините, если я был неясен, Гена. Если вы измените первую строку на: df1 = pd.DataFrame({'index': ['a1','a2','a3','a4'], 'col1': ['1232','1432','432','123'], 'col2': ['asd','dsa12','dsa12','asd2'], 'col3': ['1','2','2','3'], 'date': ['2010-01-23','2016-05-20','2010-06-20','2008-10-21'],}).set_index('index'), вы заметите, что df_finalпо-прежнему будет показывать два совпадения; но первое совпадение не должно быть совпадением, потому что значения col1 больше не совпадают; совпадают только столбцы col2 и col3. - person CAR ERL; 01.06.2016
comment
Только что заметил, что команда pd.merge, которую вы упомянули в конце, работает хорошо; но тогда я получаю только внутренние соединения; есть ли способ добавить все несовпадения из df1 и df2 в этот набор данных? Я пробовал разные версии pd.merge, но просто не могу понять... - person CAR ERL; 01.06.2016
comment
Джин, я заработал, используя версию слияния; обновил свой ответ, включив в него полный рабочий пример, который я использовал. Спасибо КУЧА! - person CAR ERL; 01.06.2016
comment
@CARERL, спасибо, приятель. Если этого достаточно, не могли бы вы отметить это как ответ? На этой ноте позвольте мне посмотреть, смогу ли я заставить внешний хак работать на вас. - person Gene Burinsky; 01.06.2016
comment
чтобы несопоставленные наблюдения оставались после слияния, попробуйте добавить аргумент how='outer' в pd.merge(), который также вернет несопоставленные значения в столбцах col1, col2 и col3 - person Gene Burinsky; 01.06.2016
comment
Давайте продолжим обсуждение в чате. - person CAR ERL; 02.06.2016
comment
Есть ли более простая версия реализации этого сейчас, особенно после того, как был представлен pandas.merge_asof (я думаю, это было вскоре после этого поста?). - person Vincent; 14.10.2020
comment
Создание нового поста для этого здесь: stackoverflow.com/questions/64355676/ - person Vincent; 14.10.2020