【问题标题】:Pandas analogue to SQL MINUS / EXCEPT operator, using multiple columnsPandas 类似于 SQL MINUS / EXCEPT 运算符,使用多列
【发布时间】:2019-10-07 16:01:00
【问题描述】:

我正在寻找SQL MINUS (AKA EXCEPT) operator 的最快和惯用的模拟。

这就是我的意思 - 给定两个 Pandas DataFrame,如下所示:

In [77]: d1
Out[77]:
   a  b  c
0  0  0  1
1  0  1  2
2  1  0  3
3  1  1  4
4  0  0  5
5  1  1  6
6  2  2  7

In [78]: d2
Out[78]:
   a  b   c
0  1  1  10
1  0  0  11
2  1  1  12

如何仅考虑列"a""b" 来查找d1 MINUS d2 的结果以获得以下结果:

In [62]: res
Out[62]:
   a  b  c
1  0  1  2
2  1  0  3
6  2  2  7

MVCE:

d1 = pd.DataFrame({
    'a': [0, 0, 1, 1, 0, 1, 2], 
    'b': [0, 1, 0, 1, 0, 1, 2], 
    'c': [1, 2, 3, 4, 5, 6, 7]
})

d2 = pd.DataFrame({
    'a': [1, 0, 1], 
    'b': [1, 0, 1], 
    'c': [10, 11, 12]
})

我尝试了什么:

In [65]: tmp1 = d1.reset_index().set_index(["a", "b"])

In [66]: idx = tmp1.index.difference(d2.set_index(["a","b"]).index)

In [67]: res = d1.loc[tmp1.loc[idx, "index"]]

In [68]: res
Out[68]:
   a  b  c
1  0  1  2
2  1  0  3
6  2  2  7

它给了我正确的结果,但我觉得必须有一种更惯用和更好/更清洁的方法来实现这一点。

PS DataFrame.isin() 方法在这种情况下无济于事,因为它会产生错误的结果集

【问题讨论】:

    标签: python sql pandas dataframe


    【解决方案1】:

    mergeindicator=True 的一种可能解决方案:

    df = (d1.reset_index()
            .merge(d2, on=['a','b'], indicator=True, how='outer', suffixes=('','_'))
            .query('_merge == "left_only"')
            .set_index('index')
            .rename_axis(None)
            .reindex(d1.columns, axis=1))
    print (df)
       a  b  c
    1  0  1  2
    2  1  0  3
    6  2  2  7
    

    isin 的解决方案:

    df = d1[~d1.set_index(["a", "b"]).index.isin(d2.set_index(["a","b"]).index)]
    print (df)
       a  b  c
    1  0  1  2
    2  1  0  3
    6  2  2  7
    

    【讨论】:

    • 我想在更大的数据集 (df1 = pd.concat([d1] * 10**4, ignore_index=True); df2 = pd.concat([d2] * 10**4, ignore_index=True);) 上测量我们的解决方案的执行速度,但在尝试使用 merge() 后,我不断收到 MemoryError - 我认为这是因为的重复项。不幸的是,在我的实际任务中,我也会有很多重复...
    • 由于outer 加入会重现m x n 数据帧,您会收到内存错误。因此,行数将根据它在 a & b @MaxU 列中找到的匹配项而爆炸式增长
    【解决方案2】:

    我在想这里有点像excel:

    d1[~d1[['a','b']].astype(str).sum(axis=1).isin(d2[['a','b']].astype(str).sum(axis=1))]
    

       a  b  c
    1  0  1  2
    2  1  0  3
    6  2  2  7
    

    【讨论】:

    • 感谢您的解决方案!顺便说一句,我已经投票了:)我也有这个想法,但我认为它可能会干扰价值观并且可能并不总是很好......
    【解决方案3】:

    我们可以在这里使用pandas.concatdrop_duplicates,并将参数传递给keep=False 删除所有重复项

    pd.concat([d1, d2]).drop_duplicates(['a', 'b'], keep=False)
    
       a  b  c
    1  0  1  2
    2  1  0  3
    6  2  2  7
    

    OP 评论后编辑

    如果您想确保考虑到 df2 中的唯一行,我们可以复制 df

    pd.concat([d1, pd.concat([d2]*2)]).drop_duplicates(['a', 'b'], keep=False)
    
       a  b  c
    1  0  1  2
    2  1  0  3
    6  2  2  7
    

    【讨论】:

    • 感谢您的解决方案!不幸的是,如果d2 的行不在d1 中,它将无法正常工作(例如尝试添加:d2.loc[3] = [10] * 3 - 您的解决方案将返回一行[10, 10, 10] - 它不应该存在)。很抱歉我在创建测试数据集的时候没有想到...
    • 我明白了,没有考虑到这一点。没问题。
    • 不得不再次看一下这个问题,我用一个应该可行的解决方案编辑了我的答案。想知道它在速度方面的表现如何@MaxU
    • 很好,很高兴我能帮上忙。顺便说一句,你的回答对我帮助很大,很高兴我能做一点点回来:) @MaxU
    【解决方案4】:

    较大数据集的执行时间比较:

    In [100]: df1 = pd.concat([d1] * 10**5, ignore_index=True)
    
    In [101]: df2 = pd.concat([d2] * 10**5, ignore_index=True)
    
    In [102]: df1.shape
    Out[102]: (700000, 3)
    
    In [103]: df2.shape
    Out[103]: (300000, 3)
    

    pd.concat().drop_duplicates()方法:

    In [10]: %%timeit
        ...: res = pd.concat([d1, pd.concat([d2]*2)]).drop_duplicates(['a', 'b'], keep=False)
        ...:
        ...:
    2.59 ms ± 129 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    多索引 NOT IS IN 方法:

    In [11]: %%timeit
        ...: res = df1[~df1.set_index(["a", "b"]).index.isin(df2.set_index(["a","b"]).index)]
        ...:
        ...:
    484 ms ± 18.6 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    多指标差分法:

    In [12]: %%timeit
        ...: tmp1 = df1.reset_index().set_index(["a", "b"])
        ...: idx = tmp1.index.difference(df2.set_index(["a","b"]).index)
        ...: res = df1.loc[tmp1.loc[idx, "index"]]
        ...:
        ...:
    1.04 s ± 20.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    merge(how="outer") 方法 - 给我一个 MemoryError:

    In [106]: %%timeit
         ...: res =  (df1.reset_index()
         ...:         .merge(df2, on=['a','b'], indicator=True, how='outer', suffixes=('','_'))
         ...:         .query('_merge == "left_only"')
         ...:         .set_index('index')
         ...:         .rename_axis(None)
         ...:         .reindex(df1.columns, axis=1))
         ...:
         ...:
    ---------------------------------------------------------------------------
    MemoryError                               Traceback (most recent call last)
    

    比较串联字符串方法:

    In [13]: %%timeit
        ...: res = df1[~df1[['a','b']].astype(str).sum(axis=1).isin(df2[['a','b']].astype(str).sum(axis=1))]
        ...:
        ...:
    2.05 s ± 65.2 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    

    【讨论】:

      【解决方案5】:

      我有类似的问题,我尝试了你的想法

      (
      In [65]: tmp1 = d1.reset_index().set_index(["a", "b"])
      
      In [66]: idx = tmp1.index.difference(d2.set_index(["a","b"]).index)
      
      In [67]: res = d1.loc[tmp1.loc[idx, "index"]]
      
      )
      

      用于测试,它有效

      但是,我在我的sqlite中使用的方式,两个具有相同结构的数据库,这意味着它的表和表的列是相同的,并且出现了一些错误,这表明这两个df似乎没有相同的形状。

      如果您愿意帮助我并想了解更多详细信息,我们可以进行进一步的交谈,非常感谢

      【讨论】:

        猜你喜欢
        • 2022-01-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-27
        • 2012-01-04
        • 1970-01-01
        • 2012-12-15
        相关资源
        最近更新 更多