Если нужен самый эффективный способ, сравните numy массивы:
def f(a, b):
#pandas 0.24+
mask = (df['col3'].to_numpy() == a) & (df['col4'].to_numpy() == b)
#all pandas versions yet
#mask = (df['col3'].values == a) & (df['col4'].values == b)
return df.loc[mask, ['col1','col2']]
Производительность : Зависит от данных, количества строк, количества совпадающих строк, но, как правило, здесь сравнение 1d-массивов выполняется быстрее:
np.random.seed(123)
N = 10000
L = list('PQRSTU')
df = pd.DataFrame({'col1': np.random.randint(10, size=N),
'col2': np.random.randint(10, size=N),
'col3': np.random.choice(L, N),
'col4': np.random.choice(L, N)})
print (df)
def f(a, b):
#pandas 0.24+
mask = (df['col3'].to_numpy() == a) & (df['col4'].to_numpy() == b)
#all pandas versions yet
#mask = (df['col3'].values == a) & (df['col4'].values == b)
return df.loc[mask, ['col1','col2']]
def f1(first, second):
return df.loc[(df['col3'] == first) & (df['col4'] == second), ['col1', 'col2']]
In [91]: %timeit (f('P', 'Q'))
2.05 ms ± 13.5 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
In [92]: %timeit (f1('P', 'Q'))
3.52 ms ± 24.3 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)