Anti-Join Pandas

У меня есть две таблицы, и я хотел бы добавить их так, чтобы сохранялись только все данные в таблице A, а данные из таблицы B добавляются только в том случае, если ее ключ уникален (значения ключа уникальны в таблице A и B, однако в в некоторых случаях ключ будет встречаться как в таблицах А, так и в В).

Я думаю, что способ сделать это будет включать какое-то соединение фильтрации (anti-join) для получения значений в таблице B, которые не встречаются в таблице A, а затем добавьте две таблицы.

Я знаком с R, и это код, который я использовал бы для этого в R.

library("dplyr")

## Filtering join to remove values already in "TableA" from "TableB"
FilteredTableB <- anti_join(TableB,TableA, by = "Key")

## Append "FilteredTableB" to "TableA"
CombinedTable <- bind_rows(TableA,FilteredTableB)

Как я могу достичь этого в python?

Ответ 1

Рассмотрим следующие фреймы данных

TableA = pd.DataFrame(np.random.rand(4, 3),
                      pd.Index(list('abcd'), name='Key'),
                      ['A', 'B', 'C']).reset_index()
TableB = pd.DataFrame(np.random.rand(4, 3),
                      pd.Index(list('aecf'), name='Key'),
                      ['A', 'B', 'C']).reset_index()

TableA

введите описание изображения здесь


TableB

введите описание изображения здесь

Это один из способов сделать то, что вы хотите

Способ 1

# Identify what values are in TableB and not in TableA
key_diff = set(TableB.Key).difference(TableA.Key)
where_diff = TableB.Key.isin(key_diff)

# Slice TableB accordingly and append to TableA
TableA.append(TableB[where_diff], ignore_index=True)

введите описание изображения здесь

Способ 2

rows = []
for i, row in TableB.iterrows():
    if row.Key not in TableA.Key.values:
        rows.append(row)

pd.concat([TableA.T] + rows, axis=1).T

Timing

4 строки с 2 перекрытиями

Метод 1 намного быстрее

введите описание изображения здесь

10 000 строк 5 000 перекрытий

петли плохие

введите описание изображения здесь

Ответ 2

У меня была та же проблема. Этот ответ с использованием how='outer' и indicator=True merge вдохновил меня для решения этого решения:

import pandas as pd
import numpy as np

TableA = pd.DataFrame(np.random.rand(4, 3),
                      pd.Index(list('abcd'), name='Key'),
                      ['A', 'B', 'C']).reset_index()
TableB = pd.DataFrame(np.random.rand(4, 3),
                      pd.Index(list('aecf'), name='Key'),
                      ['A', 'B', 'C']).reset_index()

print('TableA', TableA, sep='\n')
print('TableB', TableB, sep='\n')

TableB_only = pd.merge(
    TableA, TableB,
    how='outer', on='Key', indicator=True, suffixes=('_foo','')).query(
        '_merge == "right_only"')

print('TableB_only', TableB_only, sep='\n')

Table_concatenated = pd.concat((TableA, TableB_only), join='inner')

print('Table_concatenated', Table_concatenated, sep='\n')

Что печатает этот вывод:

TableA
  Key         A         B         C
0   a  0.035548  0.344711  0.860918
1   b  0.640194  0.212250  0.277359
2   c  0.592234  0.113492  0.037444
3   d  0.112271  0.205245  0.227157
TableB
  Key         A         B         C
0   a  0.754538  0.692902  0.537704
1   e  0.499092  0.864145  0.004559
2   c  0.082087  0.682573  0.421654
3   f  0.768914  0.281617  0.924693
TableB_only
  Key  A_foo  B_foo  C_foo         A         B         C      _merge
4   e    NaN    NaN    NaN  0.499092  0.864145  0.004559  right_only
5   f    NaN    NaN    NaN  0.768914  0.281617  0.924693  right_only
Table_concatenated
  Key         A         B         C
0   a  0.035548  0.344711  0.860918
1   b  0.640194  0.212250  0.277359
2   c  0.592234  0.113492  0.037444
3   d  0.112271  0.205245  0.227157
4   e  0.499092  0.864145  0.004559
5   f  0.768914  0.281617  0.924693

Ответ 3

Самый простой ответ:

tableB = pd.concat([tableB, pd.Series(1)], axis=1)
mergedTable = tableA.merge(tableB, how="left" on="key")

answer = mergedTable[mergedTable.iloc[:,-1].isnull()][tableA.columns.tolist()]

Должен быть самым быстрым предложенным также.

Ответ 4

У вас будут две таблицы TableA и TableB, так что оба объекта DataFrame имеют столбцы с уникальными значениями в своих соответствующих таблицах, но некоторые столбцы могут иметь значения, которые происходят одновременно (имеют одинаковые значения для строки ) в обеих таблицах.

Затем мы хотим объединить строки в TableA с строками в TableB, которые не соответствуют ни одному в TableA для столбца "Ключ". Концепция заключается в том, чтобы представить ее как сравнение двух серий переменной длины и объединение строк в одну серию sA с другими sB, если значения sB не соответствуют sA. Следующий код решает это упражнение:

import pandas as pd

TableA = pd.DataFrame([[2, 3, 4], [5, 6, 7], [8, 9, 10]])
TableB = pd.DataFrame([[1, 3, 4], [5, 7, 8], [9, 10, 0]])

removeTheseIndexes = []
keyColumnA = TableA.iloc[:,1] # your 'Key' column here
keyColumnB = TableB.iloc[:,1] # same

for i in range(0, len(keyColumnA)):
    firstValue = keyColumnA[i]
    for j in range(0, len(keyColumnB)):
        copycat = keyColumnB[j]
        if firstValue == copycat:
            removeTheseIndexes.append(j)

TableB.drop(removeTheseIndexes, inplace = True)
TableA = TableA.append(TableB)
TableA = TableA.reset_index(drop=True)

Обратите внимание, что это также влияет на данные TableB. Вы можете использовать inplace=False и повторно назначить его newTable, затем TableA.append(newTable).

# Table A
   0  1   2
0  2  3   4
1  5  6   7
2  8  9  10

# Table B
   0   1  2
0  1   3  4
1  5   7  8
2  9  10  0

# Set 'Key' column = 1
# Run the script after the loop

# Table A
   0   1   2
0  2   3   4
1  5   6   7
2  8   9  10
3  5   7   8
4  9  10   0

# Table B
   0   1  2
1  5   7  8
2  9  10  0

Ответ 5

Основываясь на одном из других предложений, здесь функция, которая должна это делать. Использование только функций панд, без зацикливания. Вы также можете использовать несколько столбцов в качестве ключа. Если вы измените строку output = merged.loc[merged.dummy_col.isna(),tableA.columns.tolist()] на output = merged.loc[~merged.dummy_col.isna(),tableA.columns.tolist()] вас есть полу-соединение.

def anti_join(tableA,tableB,on):

   #if joining on index, make it into a column
   if tableB.index.name is not None:
       dummy = tableB.reset_index()[on]
   else:
       dummy = tableB[on]

   #create a dummy columns of 1s
   if isinstance(dummy, pd.Series):
       dummy = dummy.to_frame()

   dummy.loc[:,'dummy_col'] = 1

   #preserve the index of tableA if it has one
   if tableA.index.name is not None:
       idx_name = tableA.index.name
       tableA = tableA.reset_index(drop = False)
   else:
       idx_name = None

   #do a left-join
   merged = tableA.merge(dummy,on=on,how='left')

   #keep only the non-matches
   output = merged.loc[merged.dummy_col.isna(),tableA.columns.tolist()]

   #reset the index (if applicable)
   if idx_name is not None:
       output = output.set_index(idx_name)

   return(output)

Ответ 6

indicator = True в команде merge скажет вам, какое объединение было применено, создав новый столбец _merge с тремя возможными значениями:

  • left_only
  • right_only
  • both

Вам нужно взять right_only и добавить его обратно к первой таблице. Вот и все.

И не забудьте опустить _merge столбец после того, как вы будете его использовать.

outer_join = TableA.merge(TableB, how = 'outer', indicator = True)

anti_join_B_only = outer_join[outer_join._merge == 'right_only']

anti_join_B_only = anti_join_B_only.drop('_merge', axis = 1)

combined_table = TableA.merge(anti_join_B_only, how = 'outer')

легко!