pandas - 根据行元素通过另一个数据框过滤数据框
- 2025-03-13 09:15:00
- admin 原创
- 13
问题描述:
我有一个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
只保留不在的值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)]
我认为这改进了@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's indices
joined = pd.merge(df1, df2, on=['c', 'l'], how='left')
joined
# extract desired columns where marker is NaN
joined[pd.isnull(joined['marker'])][df1.columns]
可能有一种方法可以不使用临时数组来实现这一点,但我想不出。只要您的数据不是很大,上述方法应该是一个快速且足够的答案。
解决方案 2:
这非常简洁并且效果很好:
df1 = df1[~df1.index.isin(df2.index)]
解决方案 3:
使用DataFrame.merge
&DataFrame.query
:
一种更优雅的方法是使用left join
参数,然后过滤所有具有以下内容的indicator=True
行:left_only
`query`
d = (
df1.merge(df2,
on=['c', 'l'],
how='left',
indicator=True)
.query('_merge == "left_only"')
.drop(columns='_merge')
)
print(d)
c k l
0 A 1 a
2 B 2 a
4 C 2 d
indicator=True
返回一个带有额外列的数据框,_merge
该列标记每一行left_only, both, right_only
:
df1.merge(df2, on=['c', 'l'], how='left', indicator=True)
c k l _merge
0 A 1 a left_only
1 A 2 b both
2 B 2 a left_only
3 C 2 a both
4 C 2 d left_only
解决方案 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 (idxs)
df1 = df1[pd.Series(list(zip(df1.c, df1.l)), index=df1.index).isin(idxs)]
解决方案 5:
怎么样:
df1['key'] = df1['c'] + df1['l']
d = df1[~df1['key'].isin(df2['c'] + df2['l'])].drop(['key'], axis=1)
解决方案 6:
避免创建额外列或进行合并的另一种选择是在 df2 上执行 groupby 以获取不同的 (c, l) 对,然后使用它过滤 df1。
gb = df2.groupby(("c", "l")).groups
df1[[p not in gb for p in zip(df1['c'], df1['l'])]]]
对于这个小例子,它实际上似乎比基于 pandas 的方法运行得更快一点(在我的计算机上为 666 µs 对比 1.76 ms),但我怀疑它在更大的例子上可能会更慢,因为它陷入了纯 Python 中。
解决方案 7:
您可以连接两个 DataFrames 并删除所有重复项:
df1.append(df2).drop_duplicates(subset=['c', 'l'], keep=False)
输出:
c k l
0 A 1.0 a
2 B 2.0 a
4 C 2.0 d
subset=['c', 'l']
如果有重复项,此方法无效df1
。