Pandas - фильтровать фрейм данных другим элементом данных по элементам строки
У меня есть dataframe df1
, который выглядит так:
c k l
0 A 1 a
1 A 2 b
2 B 2 a
3 C 2 a
4 C 2 d
а другой - df2
, например:
c l
0 A b
1 C a
Я хотел бы отфильтровать df1
, сохраняя только значения, которые ARE NOT находятся в df2
. Ожидается, что значения для фильтра будут соответствовать (A,b)
и (C,a)
кортежам. Пока я попытался применить метод isin
:
d = df[~(df['l'].isin(dfc['l']) & df['c'].isin(dfc['c']))]
Помимо того, что мне кажется слишком сложным, он возвращает:
c k l
2 B 2 a
4 C 2 d
но я ожидаю:
c k l
0 A 1 a
2 B 2 a
4 C 2 d
Ответы
Ответ 1
Вы можете сделать это эффективно, используя isin
для мультииндексов, построенных из нужных столбцов:
df1 = pd.DataFrame({'c': ['A', 'A', 'B', 'C', 'C'],
'k': [1, 2, 2, 2, 2],
'l': ['a', 'b', 'a', 'a', 'd']})
df2 = pd.DataFrame({'c': ['A', 'C'],
'l': ['b', 'a']})
keys = list(df2.columns.values)
i1 = df1.set_index(keys).index
i2 = df2.set_index(keys).index
df1[~i1.isin(i2)]
![enter image description here]()
Я думаю, что это улучшает аналогичное решение @IanS, потому что оно не предполагает какой-либо тип столбца (то есть он будет работать с числами, а также со строками).
(Над ответом есть правка. Ниже был мой первоначальный ответ)
Интересно! Это то, с чем я раньше не сталкивался... Я бы, вероятно, решил это путем объединения двух массивов, а затем отбрасывания строк, где определен df2
. Вот пример, который использует временный массив:
df1 = pd.DataFrame({'c': ['A', 'A', 'B', 'C', 'C'],
'k': [1, 2, 2, 2, 2],
'l': ['a', 'b', 'a', 'a', 'd']})
df2 = pd.DataFrame({'c': ['A', 'C'],
'l': ['b', 'a']})
# create a column marking df2 values
df2['marker'] = 1
# join the two, keeping all of df1 indices
joined = pd.merge(df1, df2, on=['c', 'l'], how='left')
joined
![enter image description here]()
# extract desired columns where marker is NaN
joined[pd.isnull(joined['marker'])][df1.columns]
![enter image description here]()
Может быть способ сделать это без использования временного массива, но я не могу придумать один. Пока ваши данные не огромны, вышеуказанный метод должен быть быстрым и достаточным ответом.
Ответ 2
Это довольно лаконично и хорошо работает:
df1 = df1[~df1.index.isin(df2.index)]
Ответ 3
Как насчет:
df1['key'] = df1['c'] + df1['l']
d = df1[~df1['key'].isin(df2['c'] + df2['l'])].drop(['key'], axis=1)
Ответ 4
Я думаю, что это довольно простой подход, когда вы хотите отфильтровать фрейм данных на основе нескольких столбцов из другого фрейма или даже на основе собственного списка.
df1 = pd.DataFrame({'c': ['A', 'A', 'B', 'C', 'C'],
'k': [1, 2, 2, 2, 2],
'l': ['a', 'b', 'a', 'a', 'd']})
df2 = pd.DataFrame({'c': ['A', 'C'],
'l': ['b', 'a']})
#values of df2 columns 'c' and 'l' that will be used to filter df1
idxs = list(zip(df2.c.values, df2.l.values)) #[('A', 'b'), ('C', 'a')]
#so df1 is filtered based on the values present in columns c and l of df2
df1 = df1[pd.Series(list(zip(df1.c, df1.l)), index=df1.index).isin(idxs)]
Ответ 5
Другой вариант, который позволяет избежать создания дополнительного столбца или выполнения слияния, - это сделать groupby на df2, чтобы получить отдельные (c, l) пары, а затем просто фильтровать df1 с помощью этого.
gb = df2.groupby(("c", "l")).groups
df1[[p not in gb for p in zip(df1['c'], df1['l'])]]]
Для этого небольшого примера он, по-видимому, работает немного быстрее, чем подход, основанный на pandas (666 мкс против 1,76 мс на моей машине), но я подозреваю, что он может быть более медленным на более крупных примерах, поскольку он переходит в чистый Python.