【问题标题】:pandas get rows which are NOT in other dataframepandas 获取不在其他数据框中的行
【发布时间】:2015-05-08 05:29:51
【问题描述】:

我有两个 pandas 数据框,它们有一些共同的行。

假设 dataframe2 是 dataframe1 的子集。

如何获取 dataframe1 中不在 dataframe2 中的行?

df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})

df1

   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14

df2

   col1  col2
0     1    10
1     2    11
2     3    12

预期结果:

   col1  col2
3     4    13
4     5    14

【问题讨论】:

  • @TedPetrou 我看不出你提供的答案是正确的。如果我有两个数据框,其中一个是另一个的子集,我需要删除所有这些行,它们在子集中。我不想删除重复项。我完全想删除子集。

标签: python pandas dataframe


【解决方案1】:

一种方法是将内部合并的结果存储在两个 dfs 中,然后我们可以简单地选择当一列的值不在此公共范围内时的行:

In [119]:

common = df1.merge(df2,on=['col1','col2'])
print(common)
df1[(~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))]
   col1  col2
0     1    10
1     2    11
2     3    12
Out[119]:
   col1  col2
3     4    13
4     5    14

编辑

您发现的另一种方法是使用isin,这将产生您可以删除的NaN 行:

In [138]:

df1[~df1.isin(df2)].dropna()
Out[138]:
   col1  col2
3     4    13
4     5    14

但是,如果 df2 没有以相同的方式开始行,那么这将不起作用:

df2 = pd.DataFrame(data = {'col1' : [2, 3,4], 'col2' : [11, 12,13]})

将产生整个df:

In [140]:

df1[~df1.isin(df2)].dropna()
Out[140]:
   col1  col2
0     1    10
1     2    11
2     3    12
3     4    13
4     5    14

【讨论】:

  • df1[~df1.isin(df2)].dropna(how = 'all') 似乎可以解决问题。无论如何,谢谢 - 您的回答帮助我找到了解决方案。
  • 您愿意解释一下~ 在您的代码df1[~df1.isin(df2)] 中的作用吗?不能用谷歌搜索任何东西,因为它只是一个符号。谢谢。
  • @BowenLiu 它否定了表达式,基本上它说选择所有不是 IN 而不是 IN。
  • @thinknicethings,它可能更简单:df1[~df1.index.isin(df2.index)]
【解决方案2】:

正如已经暗示的那样,isin 要求列和索引相同才能进行匹配。如果匹配只应在行内容上,则获取用于过滤存在的行的掩码的一种方法是将行转换为(多)索引:

In [77]: df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 'col2' : [10, 11, 12, 13, 14, 10]})
In [78]: df2 = pandas.DataFrame(data = {'col1' : [1, 3, 4], 'col2' : [10, 12, 13]})
In [79]: df1.loc[~df1.set_index(list(df1.columns)).index.isin(df2.set_index(list(df2.columns)).index)]
Out[79]:
   col1  col2
1     2    11
4     5    14
5     3    10

如果应该考虑索引,set_index 有关键字参数 append 以将列附加到现有索引。如果列不对齐,可以将 list(df.columns) 替换为列规范以对齐数据。

pandas.MultiIndex.from_tuples(df<N>.to_records(index = False).tolist())

也可以用于创建索引,但我怀疑这是否更有效。

【讨论】:

  • 这确实有用且高效。以前的选项不适用于我的数据。谢谢!
  • 谢谢你!这是一个非常适合我的例子。
【解决方案3】:

假设您有两个数据框,df_1 和 df_2 具有多个字段(列名称),并且您想根据某些字段(例如 fields_x、fields_y)找到 df_1 中唯一不在 df_2 中的条目,请遵循以下步骤。

Step1.分别在df_1和df_2中添加列key1和key2。

Step2.合并数据框,如下所示。 field_x 和 field_y 是我们想要的列。

Step3.仅选择 df_1 中 key1 不等于 key2 的那些行。

Step4.Drop key1 和 key2。

此方法将解决您的问题,并且即使使用大数据集也能快速运行。我已经对超过 1,000,000 行的数据框进行了尝试。

df_1['key1'] = 1
df_2['key2'] = 1
df_1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'left')
df_1 = df_1[~(df_1.key2 == df_1.key1)]
df_1 = df_1.drop(['key1','key2'], axis=1)

【讨论】:

  • 我不认为这在技术上是他想要的 - 他想知道哪些行对哪个 df 是唯一的。但是,我认为这个解决方案会返回一个 df 行,这些行对于第一个 df 或第二个 df 是唯一的。
【解决方案4】:

您可以使用isin(dict) 方法来做到这一点:

In [74]: df1[~df1.isin(df2.to_dict('l')).all(1)]
Out[74]:
   col1  col2
3     4    13
4     5    14

解释:

In [75]: df2.to_dict('l')
Out[75]: {'col1': [1, 2, 3], 'col2': [10, 11, 12]}

In [76]: df1.isin(df2.to_dict('l'))
Out[76]:
    col1   col2
0   True   True
1   True   True
2   True   True
3  False  False
4  False  False

In [77]: df1.isin(df2.to_dict('l')).all(1)
Out[77]:
0     True
1     True
2     True
3    False
4    False
dtype: bool

【讨论】:

  • 正如 Ted Petrou 指出的那样,这个解决方案会导致错误的结果,我可以确认。
【解决方案5】:

有点晚了,但可能值得检查 pd.merge 的“indicator”参数。

有关示例,请参见其他问题: Compare PandaS DataFrames and return rows that are missing from the first one

【讨论】:

【解决方案6】:

我的做法是添加一个数据框独有的新列,并使用它来选择是否保留条目

df2[col3] = 1
df1 = pd.merge(df_1, df_2, on=['field_x', 'field_y'], how = 'outer')
df1['Empt'].fillna(0, inplace=True)

这使得 df1 中的每个条目都有一个代码 - 如果它对 df1 是唯一的,则为 0,如果它在两个数据帧中,则为 1。然后,您可以使用它来限制您想要的内容

answer = nonuni[nonuni['Empt'] == 0]

【讨论】:

    【解决方案7】:

    假设数据帧中​​的索引是一致的(不考虑实际的 col 值):

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

    【讨论】:

      【解决方案8】:

      这个怎么样:

      df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 
                                     'col2' : [10, 11, 12, 13, 14]}) 
      df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 
                                     'col2' : [10, 11, 12]})
      records_df2 = set([tuple(row) for row in df2.values])
      in_df2_mask = np.array([tuple(row) in records_df2 for row in df1.values])
      result = df1[~in_df2_mask]
      

      【讨论】:

        【解决方案9】:

        当前选择的解决方案产生不正确的结果。为了正确解决这个问题,我们可以执行从df1df2 的左连接,确保首先获得df2 的唯一行。

        首先,我们需要修改原来的DataFrame,添加数据为[3, 10]的行。

        df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                                   'col2' : [10, 11, 12, 13, 14, 10]}) 
        df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                                   'col2' : [10, 11, 12]})
        
        df1
        
           col1  col2
        0     1    10
        1     2    11
        2     3    12
        3     4    13
        4     5    14
        5     3    10
        
        df2
        
           col1  col2
        0     1    10
        1     2    11
        2     3    12
        

        执行左连接,消除df2 中的重复项,以便df1 的每一行与df2 的一行恰好连接。使用参数indicator 可以返回一个额外的列,指示该行来自哪个表。

        df_all = df1.merge(df2.drop_duplicates(), on=['col1','col2'], 
                           how='left', indicator=True)
        df_all
        
           col1  col2     _merge
        0     1    10       both
        1     2    11       both
        2     3    12       both
        3     4    13  left_only
        4     5    14  left_only
        5     3    10  left_only
        

        创建一个布尔条件:

        df_all['_merge'] == 'left_only'
        
        0    False
        1    False
        2    False
        3     True
        4     True
        5     True
        Name: _merge, dtype: bool
        

        为什么其他解决方案是错误的

        一些解决方案会犯同样的错误 - 他们只检查每个值在每一列中是独立的,而不是在同一行中。添加最后一行,这是唯一的,但具有来自df2 的两列的值会暴露错误:

        common = df1.merge(df2,on=['col1','col2'])
        (~df1.col1.isin(common.col1))&(~df1.col2.isin(common.col2))
        0    False
        1    False
        2    False
        3     True
        4     True
        5    False
        dtype: bool
        

        这个解决方案得到同样的错误结果:

        df1.isin(df2.to_dict('l')).all(1)
        

        【讨论】:

        • 但是,我想,他们假设 col1 作为索引是唯一的(问题中没有提到,但很明显)。因此,如果从来没有这样的情况,即 col2 的两个值对应 col1 的相同值(不能有两个 col1=3 行),那么上面的答案是正确的。
        • 肯定不明显,所以你的观点是无效的。我的解决方案可以推广到更多案例。
        • 问题,创建切片而不是布尔数组不是更容易吗?因为目标是获取行。
        • 使用df_all[df_all['_merge'] == 'left_only'] 获取结果的df
        • 对于新来的,不加解释的多出一行是很混乱的。然后@gies0r 使这个解决方案变得更好。此外,我建议使用how='outer',以便_merge 列具有左/右/两者,当未来的读者尝试将解决方案应用于他们的问题时,这更容易理解。
        【解决方案10】:

        你也可以连接df1,df2:

        x = pd.concat([df1, df2])
        

        然后删除所有重复项:

        y = x.drop_duplicates(keep=False, inplace=False)
        

        【讨论】:

        • 这将返回任一集合中的所有数据,而不仅仅是 df1 中的数据。
        【解决方案11】:

        这是解决此问题的另一种方法:

        df1[~df1.index.isin(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]
        

        或者:

        df1.loc[df1.index.difference(df1.merge(df2, how='inner', on=['col1', 'col2']).index)]
        

        【讨论】:

          【解决方案12】:
          使用合并函数提取不同的行
          df = df.merge(same.drop_duplicates(), on=['col1','col2'], 
                         how='left', indicator=True)
          
          将不同的行保存在 CSV
          df[df['_merge'] == 'left_only'].to_csv('output.csv')
          

          【讨论】:

            【解决方案13】:

            这是最好的方法:

            df = df1.drop_duplicates().merge(df2.drop_duplicates(), on=df2.columns.to_list(), 
                               how='left', indicator=True)
            df.loc[df._merge=='left_only',df.columns!='_merge']
            

            请注意,drop duplicated 用于最小化比较。没有它们也可以。最好的方法是比较行内容本身,而不是索引或一/两列,并且相同的代码也可用于其他过滤器,如“both”和“right_only”,以获得类似的结果。对于这种语法,数据帧可以有任意数量的列,甚至可以有不同的索引。只有列应该出现在两个数据框中。

            为什么这是最好的方法?

            1. index.difference 仅适用于基于唯一索引的比较
            2. pandas.concat() 加上 drop_duplicated() 并不理想,因为它还会删除可能仅在您想要保留的数据框中并出于正当理由而重复的行。

            【讨论】:

              【解决方案14】:

              更简单、更简单、更优雅

              uncommon_indices = np.setdiff1d(df1.index.values, df2.index.values)
              new_df = df1.loc[uncommon_indices,:]
              

              【讨论】:

                【解决方案15】:

                我认为那些包含合并的答案非常慢。因此,我建议另一种获取两个数据帧之间不同行的方法:

                df1 = pandas.DataFrame(data = {'col1' : [1, 2, 3, 4, 5], 'col2' : [10, 11, 12, 13, 14]}) 
                df2 = pandas.DataFrame(data = {'col1' : [1, 2, 3], 'col2' : [10, 11, 12]})
                

                免责声明:如果您对两个数据框不同的特定列感兴趣,我的解决方案将有效。如果您只对那些所有列都相等的行感兴趣,请不要使用这种方法。

                假设 col1 是一种 ID,您只想获取那些不包含在两个数据帧中的行:

                ids_in_df2 = df2.col1.unique()
                not_found_ids = df[~df['col1'].isin(ids_in_df2 )]
                

                就是这样。您会得到一个数据框,其中仅包含 col1 在两个数据框中都没有出现的那些行。

                【讨论】:

                  【解决方案16】:

                  我有一个更简单的方法,只需两个简单的步骤: 正如OP提到的假设dataframe2是dataframe1的子集,2个dataframe中的列是相同的,

                  df1 = pd.DataFrame(data = {'col1' : [1, 2, 3, 4, 5, 3], 
                                             'col2' : [10, 11, 12, 13, 14, 10]}) 
                  df2 = pd.DataFrame(data = {'col1' : [1, 2, 3],
                                             'col2' : [10, 11, 12]})
                  
                  ### Step 1: just append the 2nd df at the end of the 1st df 
                  df_both = df1.append(df2)
                  
                  ### Step 2: drop rows which contain duplicates, Drop all duplicates.
                  df_dif = df_both.drop_duplicates(keep=False)
                  
                  ## mission accompliched!
                  df_dif
                  Out[20]: 
                     col1  col2
                  3     4    13
                  4     5    14
                  5     3    10
                  

                  【讨论】:

                    猜你喜欢
                    • 2023-01-13
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多