Подтвердить что ты не робот

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
4b9b3361

Ответ 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.