【问题标题】:pandas - filter dataframe by another dataframe by row elementspandas - 按行元素通过另一个数据框过滤数据框
【发布时间】:2016-01-21 19:06:55
【问题描述】:

我有一个数据框 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

【问题讨论】:

  • 如何将cl 两列的值连接起来,并将其用作您的键?

标签: python pandas dataframe


【解决方案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]

可能有一种方法可以在不使用临时数组的情况下做到这一点,但我想不出一个。只要您的数据不是很大,上述方法应该是一个快速且足够的答案。

【讨论】:

  • 感谢您的署名 :) 我认为您应该让您的编辑成为一个新的答案,希望能被接受。我一定会投票给它!
  • 我将编辑更改为主要答案。谢谢!
  • 好方法!我认为这样做更容易,感谢大家的帮助!
  • @jakevdp,谢谢,我试过了,它非常适合这种情况。我有一个稍微复杂的方案,其中df2 = pd.DataFrame({'c': ['A', *], 'l': [*, 'a']})* 我的意思是通配符,所以值可以是任何值。所以df1[~i1.isin(i2)] 的输出应该是:pd.DataFrame({'c': ['C'], 'k': [2], 'l': ['d']})。是否可以通过修改上述来实现?
  • 您的初始答案会创建一个标记列,但 pd.merge() 现在包含一个参数“indicator”。如果您选择indicator=True,则会添加一个额外的列(称为“_merge”),它本身就是新创建的合并df 上的一个标记。然后,您将过滤joined['_merge']=='left_only'。
【解决方案2】:

怎么样:

df1['key'] = df1['c'] + df1['l']
d = df1[~df1['key'].isin(df2['c'] + df2['l'])].drop(['key'], axis=1)

【讨论】:

  • 我认为如果您提供更多信息,您的回答会更加有力。您能否edit 这个答案包含一些关于为什么有人应该使用这种方法的信息,或者至少这个代码完成了什么?如果您无法提出任何详细说明,请考虑一个场景:如果我盲目地将您的代码复制并粘贴到我的应用程序中,是否有任何我应该担心的边缘情况?什么时候应该避免使用这种方法?
【解决方案3】:

另一个避免创建额外列或进行合并的选项是在 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 .

【讨论】:

    【解决方案4】:

    这很简洁,效果很好:

    df1 = df1[~df1.index.isin(df2.index)]
    

    【讨论】:

    • 虽然此代码可能会回答问题,但提供有关它如何和/或为什么解决问题的额外上下文将提高​​答案的长期价值。请阅读此how-to-answer 以提供高质量的答案。
    • 这只是因为示例数据是对齐的,如果键没有对齐,这将在任何意义上都失败。不知道这是如何获得 14 票的。
    【解决方案5】:

    我认为这是一种非常简单的方法,当您想根据来自另一个数据帧的多列或什至基于自定义列表过滤数据帧时。

    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)]
    

    【讨论】:

      【解决方案6】:

      使用DataFrame.merge & DataFrame.query

      更优雅的方法是使用参数indicator=True 执行left join,然后使用query 过滤所有left_only 的行:

      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
      

      【讨论】:

      • 确实是迄今为止最优雅的解决方案。我以前只使用合并和过滤,但.query() 开辟了一个全新的世界。
      【解决方案7】:

      您可以连接两个 DataFrame 并删除所有重复项:

      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
      

      如果您在df1 中有重复的subset=['c', 'l'],则此方法不起作用。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-03-08
        • 1970-01-01
        • 1970-01-01
        • 2017-06-28
        • 2023-03-12
        • 1970-01-01
        • 2013-06-07
        相关资源
        最近更新 更多