Join two DataFrames on common columns only if the difference in a separate column is within range [-n, +n]

We can merge, then perform a query to drop rows not within the range:

(df1.merge(df2, on=['Date', 'BillNo.'])
    .query('abs(Amount_x - Amount_y) <= 5')
    .drop('Amount_x', axis=1))

         Date    BillNo.  Amount_y
0  10/08/2020  ABBCSQ1ZA       876
1  10/16/2020  AA171E1Z0      5491

This works well as long as there is only one row that corresponds to a specific (Date, BillNo) combination in each frame.


You could use merge_asof:

udf2 = df2.drop_duplicates().sort_values('Amount')
res = pd.merge_asof(udf2, df1.sort_values('Amount').assign(indicator=1), on='Amount', by=['Date', 'BillNo.'],
                    direction='nearest', tolerance=5)
res = res.dropna().drop('indicator', 1)

print(res)

Output

         Date    BillNo.  Amount
2  10/08/2020  ABBCSQ1ZA     876
3  10/16/2020  AA171E1Z0    5491

We can set Date and BillNo. as index as subtract both the dataframe and filter out only values b/w -5 to 5.

d1 = df1.set_index(['Date', 'BillNo.'])
d2 = df2.set_index(['Date', 'BillNo.'])

idx = (d1-d2).query('Amount>=-5 & Amount<=5').index

d1.loc[idx].reset_index()
         Date    BillNo.  Amount
0  10/08/2020  ABBCSQ1ZA     878
1  10/16/2020  AA171E1Z0    5490

d2.loc[idx].reset_index()
         Date    BillNo.  Amount
0  10/08/2020  ABBCSQ1ZA     876
1  10/16/2020  AA171E1Z0    5491

To make it more generic to work with any n.

n = 5
idx = (d1-d2).query('Amount>=-@n & Amount<=@n').index

Or

lower_limit = -2 # Example, can be anything
upper_limit = 5  # Example, can be anything
idx = (d1-d2).query('Amount>=@lower_limit & Amount<=@upper_limit').index