【问题标题】:set difference for pandas为熊猫设置差异
【发布时间】:2013-08-13 09:31:39
【问题描述】:

一个简单的熊猫问题:

是否有drop_duplicates() 功能可以删除复制中涉及的每一行?

一个等价的问题如下:pandas 对数据帧有什么不同吗?

例如:

In [5]: df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})

In [6]: df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})

In [7]: df1
Out[7]: 
   col1  col2
0     1     2
1     2     3
2     3     4

In [8]: df2
Out[8]: 
   col1  col2
0     4     6
1     2     3
2     5     5

所以也许像df2.set_diff(df1) 这样的东西会产生这个:

   col1  col2
0     4     6
2     5     5

但是,我不想依赖索引,因为就我而言,我必须处理具有不同索引的数据帧。

顺便说一句,我最初考虑的是对当前drop_duplicates() 方法的扩展,但现在我意识到使用集合论属性的第二种方法通常会更有用。不过,这两种方法都解决了我当前的问题。

谢谢!

【问题讨论】:

  • 是的,有 drop_duplicates 方法...检查文档pandas.pydata.org。根据数据的结构方式,您也应该能够进行集合操作。可以肯定地用另一个框架中的项目替换框架中的项目。不确定是否可以检查所有列的唯一性
  • 你能指出如何执行集合操作吗?
  • 您能提供一个示例数据并告诉我们您打算完成什么吗?
  • 当然。我会在几分钟内更新。
  • 如果没有重复......哪个数据框有准确的。即您只是想找到独特的项目还是需要将它们与一些额外的逻辑合并?

标签: python pandas dataframe


【解决方案1】:

有点复杂,但如果你想完全忽略索引数据。将数据帧的内容转换为包含列的元组集:

ds1 = set(map(tuple, df1.values))
ds2 = set(map(tuple, df2.values))

此步骤也将删除数据框中的任何重复项(忽略索引)

set([(1, 2), (3, 4), (2, 3)])   # ds1

然后可以使用 set 方法来查找任何内容。例如寻找差异:

ds1.difference(ds2)

给出: 设置([(1, 2), (3, 4)])

如果需要,可以将其带回数据框。注意必须将集合转换为列表 1,因为集合不能用于构造数据框:

pd.DataFrame(list(ds1.difference(ds2)))

【讨论】:

  • 太棒了。这非常有效。显然,它会重新排序行,但这不是问题。也许与此相关的唯一问题是 df1 需要首先转换为一个集合,以便删除 df1 中可能不需要的重复项。非常感谢!
  • yes 根据定义设置会创建一个无序的数据结构。不确定是否有纯 pandas 替代方案,但看到索引数据对您并不重要,使用内置方法是有意义的。请务必查看@Jeff 解决方案。他是一位真正的熊猫专家,可能比我的代码更快。同样保留在 pandas 数据结构中将保留索引数据,这可能对您仍然有用。
  • 不幸的是,我发现了一个小错误。如果您正在处理需要合理精度的数字,这将导致一些问题,因为它会四舍五入到最接近的小数。就我而言,它是小数点后的第 7 位。
  • 这个计算复杂度是多少?这看起来慢得令人无法接受,不是吗?
【解决方案2】:

在 Pandas 1.1.0 中,您可以使用 value_counts 计算唯一行并找出计数之间的差异:

df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})

diff = df2.value_counts().sub(df1.value_counts(), fill_value=0)

结果:

col1  col2
1     2      -1.0
2     3       0.0
3     4      -1.0
4     6       1.0
5     5       1.0
dtype: float64

获得正数:

diff[diff > 0].reset_index(name='counts')


   col1  col2  counts
0     4     6     1.0
1     5     5     1.0

【讨论】:

    【解决方案3】:

    这是另一个保留索引并且不需要两个数据帧中的索引相同的答案。 (编辑:事先确保 df2 中没有重复项)

    pd.concat([df2, df1, df1]).drop_duplicates(keep=False)
    

    速度很快,结果是

       col1  col2
    0     4     6
    2     5     5
    

    【讨论】:

    • 这很聪明。当然,它不具备集合论函数的灵活性。
    • 仅供参考的链接已损坏
    • 这个答案意味着 df2 中有任何重复的行(df2 已经在一组行中)
    • @guilloptero:您可以在运行前删除 df2 中的任何重复项来进行修改
    【解决方案4】:
    from pandas import  DataFrame
    
    df1 = DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
    df2 = DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})
    
    
    print(df2[~df2.isin(df1).all(1)])
    print(df2[(df2!=df1)].dropna(how='all'))
    print(df2[~(df2==df1)].dropna(how='all'))
    

    【讨论】:

    • 我相信这个解决方案只有在两个 DataFrame 上的索引相同时才有效。 @Joop 有一个没有这种情况的答案。
    • 我希望为我们这些试图跟进的人多解释一下这个答案。我认为所有三个答案都是相同的,但是它们在结果中给出了不同的数据类型。我认为 ~ 否定了操作,但是 .all(1) 的目的是什么?
    • @David, .all(axis) - 轴:{0, 1}。 0 表示按行,1 表示按列。所以要检查所有列(轴 = 1)是否为空.. 使用 .all(1)。不带 .all(1) 的检查结果
    • 如果您的数据中有无,则第一个不起作用。示例:df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,None,4]})df2 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,None,4]})print(df2[~df2.isin(df1).all(1)])
    【解决方案5】:

    Numpy 的 setdiff1d 可以工作,而且可能更快。

    对于每一列: np.setdiff1(df1.col1.values, df2.col1.values)

    比如:

    setdf = pd.DataFrame({
        col: np.setdiff1d(getattr(df1, col).values, getattr(df2, col).values)
        for col in df1.columns
    })
    

    numpy.setdiff1d docs

    【讨论】:

      【解决方案6】:

      编辑:您现在可以直接从 pandas 0.24.0 开始的数据帧创建 MultiIndex 对象,这大大简化了此答案的语法

      df1mi = pd.MultiIndex.from_frame(df1)
      df2mi = pd.MultiIndex.from_frame(df2)
      dfdiff = df2mi.difference(df1mi).to_frame().reset_index(drop=True)
      

      原答案

      Pandas MultiIndex 对象具有作为方法实现的快速集合操作,因此您可以将 DataFrame 转换为 MultiIndex,使用 difference() 方法,然后将结果转换回 DataFrame。这个解决方案应该比这里给出的解决方案快得多(从我的简短测试来看大约 100 倍或更多),并且它不依赖于原始帧的行索引。正如 Piotr 在他的回答中提到的那样,这将因空值而失败,因为 np.nan != np.nan。 df2 中具有空值的任何行将始终出现在差异中。此外,两个 DataFrame 的列顺序应相同。

      df1mi = pd.MultiIndex.from_arrays(df1.values.transpose(), names=df1.columns)
      df2mi = pd.MultiIndex.from_arrays(df2.values.transpose(), names=df2.columns)
      dfdiff = df2mi.difference(df1mi).to_frame().reset_index(drop=True)
      

      【讨论】:

        【解决方案7】:

        即使您在两个数据框中都有多个列,这也应该有效。但请确保两个数据框的列名完全相同。

        set_difference = pd.concat([df2, df1, df1]).drop_duplicates(keep=False)
        

        您还可以使用多个列:

        col_names=['col_1','col_2']
        set_difference = pd.concat([df2[col_names], df1[col_names], 
        df1[col_names]]).drop_duplicates(keep=False)
        

        【讨论】:

          【解决方案8】:

          假设:

          1. df1 和 df2 具有相同的列
          2. 这是一个集合操作,因此忽略重复项
          3. 集合不是特别大,所以不用担心内存问题
          union = pd.concat([df1,df2])
          sym_diff = union[~union.duplicated(keep=False)]
          union_of_df1_and_sym_diff = pd.concat([df1, sym_diff])
          diff = union_of_df1_and_sym_diff[union_of_df1_and_sym_diff.duplicated()]
          

          【讨论】:

            【解决方案9】:

            有 3 种方法可行,但其中有两种方法存在缺陷。

            方法一(哈希法):

            它适用于我测试的所有案例。

            df1.loc[:, "hash"] = df1.apply(lambda x: hash(tuple(x)), axis = 1)
            df2.loc[:, "hash"] = df2.apply(lambda x: hash(tuple(x)), axis = 1)
            df1 = df1.loc[~df1["hash"].isin(df2["hash"]), :]
            

            方法二(字典法):

            如果 DataFrames 包含日期时间列,则会失败。

            df1 = df1.loc[~df1.isin(df2.to_dict(orient="list")).all(axis=1), :]
            

            方法3(MultiIndex方法):

            我遇到了在具有 None 或 NaN 的列上失败的情况。

            df1 = df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)
            

            【讨论】:

              【解决方案10】:

              我不确定pd.concat() 是如何隐式连接重叠列的,但我必须对@radream 的回答进行一些调整。

              从概念上讲,多列上的集合差异 (symmetric) 是集合并集(外连接)减去集合交集(或内连接):

              df1 = pd.DataFrame({'col1':[1,2,3], 'col2':[2,3,4]})
              df2 = pd.DataFrame({'col1':[4,2,5], 'col2':[6,3,5]})
              o = pd.merge(df1, df2, how='outer')
              i = pd.merge(df1, df2)
              set_diff = pd.concat([o, i]).drop_duplicates(keep=False)
              

              这会产生:

                 col1  col2
              0     1     2
              2     3     4
              3     4     6
              4     5     5
              

              【讨论】:

                【解决方案11】:

                通过合并获取交集的索引,然后删除它们:

                >>> df_all = pd.DataFrame(np.arange(8).reshape((4,2)), columns=['A','B']); df_all
                   A  B
                0  0  1
                1  2  3
                2  4  5
                3  6  7
                >>> df_completed = df_all.iloc[::2]; df_completed
                   A  B
                0  0  1
                2  4  5
                >>> merged = pd.merge(df_all.reset_index(), df_completed); merged
                   index  A  B
                0      0  0  1
                1      2  4  5
                >>> df_pending = df_all.drop(merged['index']); df_pending
                   A  B
                1  2  3
                3  6  7
                

                【讨论】:

                  【解决方案12】:

                  按要映射的对象的列应用(df2);查找不在集合中的行(isin 类似于集合运算符)

                  In [32]: df2.apply(lambda x: df2.loc[~x.isin(df1[x.name]),x.name])
                  Out[32]: 
                     col1  col2
                  0     4     6
                  2     5     5
                  

                  同样的事情,但包含 df1 中的所有值,但仍包含 df2 中的每列

                  In [33]: df2.apply(lambda x: df2.loc[~x.isin(df1.values.ravel()),x.name])
                  Out[33]: 
                     col1  col2
                  0   NaN     6
                  2     5     5
                  

                  第二个例子

                  In [34]: g = pd.DataFrame({'x': [1.2,1.5,1.3], 'y': [4,4,4]})
                  
                  In [35]: g.columns=df1.columns
                  
                  In [36]: g
                  Out[36]: 
                     col1  col2
                  0   1.2     4
                  1   1.5     4
                  2   1.3     4
                  
                  In [32]: g.apply(lambda x: g.loc[~x.isin(df1[x.name]),x.name])
                  Out[32]: 
                     col1  col2
                  0   1.2   NaN
                  1   1.5   NaN
                  2   1.3   NaN
                  

                  请注意,在 0.13 中,框架级别将有一个 isin 运算符,因此应该可以使用类似:df2.isin(df1) 的内容

                  【讨论】:

                  • 有趣。由于某种原因,我无法让它工作。它返回了很多 NaN,所以我需要看看出了什么问题。
                  • 我觉得有问题。如果你有这样的东西:g = pd.DataFrame({'x': [1.2,1.5,1.3], 'y': [4,4,4]}) 并且你想删除g.ix[[0,2]],那么这将不会选择正确的行,因为它会检查 g 的每个元素是否在 g.ix[[0,2]] 中。第二行包含一个数字 (4),它等于 g.ix[[0,2]] 之一(实际上,其中两个是相等的)。
                  • 我不清楚你的评论; g.set_diff(df1)(或相反)的结果是什么
                  • g.set_diff(df1) 应该返回 g 中不包含在 df1 中的行。
                  猜你喜欢
                  • 1970-01-01
                  • 2021-01-05
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-10-07
                  • 1970-01-01
                  • 2019-07-17
                  • 1970-01-01
                  相关资源
                  最近更新 更多