比较 Python Pandas DataFrames 以匹配行

Posted

技术标签:

【中文标题】比较 Python Pandas DataFrames 以匹配行【英文标题】:Compare Python Pandas DataFrames for matching rows 【发布时间】:2015-06-10 10:26:41 【问题描述】:

我在 Pandas 中有这个 DataFrame (df1):

df1 = pd.DataFrame(np.random.rand(10,4),columns=list('ABCD'))
print df1

       A         B         C         D
0.860379  0.726956  0.394529  0.833217
0.014180  0.813828  0.559891  0.339647
0.782838  0.698993  0.551252  0.361034
0.833370  0.982056  0.741821  0.006864
0.855955  0.546562  0.270425  0.136006
0.491538  0.445024  0.971603  0.690001
0.911696  0.065338  0.796946  0.853456
0.744923  0.545661  0.492739  0.337628
0.576235  0.219831  0.946772  0.752403
0.164873  0.454862  0.745890  0.437729

我想检查df1 中是否存在来自另一个数据框 (df2) 的任何行(所有列)。这里是df2

df2 = df1.ix[4:8]
df2.reset_index(drop=True,inplace=True)
df2.loc[-1] = [2, 3, 4, 5]
df2.loc[-2] = [14, 15, 16, 17]
df2.reset_index(drop=True,inplace=True)
print df2

           A         B         C         D
    0.855955  0.546562  0.270425  0.136006
    0.491538  0.445024  0.971603  0.690001
    0.911696  0.065338  0.796946  0.853456
    0.744923  0.545661  0.492739  0.337628
    0.576235  0.219831  0.946772  0.752403
    2.000000  3.000000  4.000000  5.000000
   14.000000 15.000000 16.000000 17.000000

我尝试使用df.lookup 一次搜索一行。我是这样做的:

list1 = df2.ix[0].tolist()
cols = df1.columns.tolist()
print df1.lookup(list1, cols)

但我收到此错误消息:

  File "C:\Users\test.py", line 19, in <module>
    print df1.lookup(list1, cols)
  File "C:\python27\lib\site-packages\pandas\core\frame.py", line 2217, in lookup
    raise KeyError('One or more row labels was not found')
KeyError: 'One or more row labels was not found'

我也试过.all() 使用:

print (df2 == df1).all(1).any()

但我收到此错误消息:

  File "C:\Users\test.py", line 12, in <module>
    print (df2 == df1).all(1).any()
  File "C:\python27\lib\site-packages\pandas\core\ops.py", line 884, in f
    return self._compare_frame(other, func, str_rep)
  File "C:\python27\lib\site-packages\pandas\core\frame.py", line 3010, in _compare_frame
    raise ValueError('Can only compare identically-labeled '
ValueError: Can only compare identically-labeled DataFrame objects

我也试过这样isin()

print df2.isin(df1)

但我到处都有False,这是不正确的:

    A      B      C      D
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False
False  False  False  False

是否可以通过将数据帧与另一个数据帧的行进行比较来搜索数据帧中的一组行?

编辑: 如果df1 中也存在这些行,是否可以删除df2 行?

【问题讨论】:

【参考方案1】:

您的问题的一个可能解决方案是使用merge。检查 df1 中是否存在来自另一个数据帧 (df2) 的任何行(所有列)相当于确定两个数据帧的交集。这可以使用以下函数来完成:

pd.merge(df1, df2, on=['A', 'B', 'C', 'D'], how='inner')

例如,如果 df1 是

    A           B            C          D
0   0.403846    0.312230    0.209882    0.397923
1   0.934957    0.731730    0.484712    0.734747
2   0.588245    0.961589    0.910292    0.382072
3   0.534226    0.276908    0.323282    0.629398
4   0.259533    0.277465    0.043652    0.925743
5   0.667415    0.051182    0.928655    0.737673
6   0.217923    0.665446    0.224268    0.772592
7   0.023578    0.561884    0.615515    0.362084
8   0.346373    0.375366    0.083003    0.663622
9   0.352584    0.103263    0.661686    0.246862

而 df2 被定义为:

     A          B            C           D
0   0.259533    0.277465    0.043652    0.925743
1   0.667415    0.051182    0.928655    0.737673
2   0.217923    0.665446    0.224268    0.772592
3   0.023578    0.561884    0.615515    0.362084
4   0.346373    0.375366    0.083003    0.663622
5   2.000000    3.000000    4.000000    5.000000
6   14.000000   15.000000   16.000000   17.000000

函数pd.merge(df1, df2, on=['A', 'B', 'C', 'D'], how='inner') 产生:

     A           B           C           D
0   0.259533    0.277465    0.043652    0.925743
1   0.667415    0.051182    0.928655    0.737673
2   0.217923    0.665446    0.224268    0.772592
3   0.023578    0.561884    0.615515    0.362084
4   0.346373    0.375366    0.083003    0.663622

结果是 df1 和 df2 中的所有行(所有列)。

如果 df1 和 df2 中的列不相同,我们也可以修改此示例,并仅比较列子集的相同行值。如果我们修改原来的例子:

df1 = pd.DataFrame(np.random.rand(10,4),columns=list('ABCD'))
df2 = df1.ix[4:8]
df2.reset_index(drop=True,inplace=True)
df2.loc[-1] = [2, 3, 4, 5]
df2.loc[-2] = [14, 15, 16, 17]
df2.reset_index(drop=True,inplace=True)
df2 = df2[['A', 'B', 'C']] # df2 has only columns A B C

然后我们可以在两个数据框之间使用common_cols = list(set(df1.columns) &amp; set(df2.columns))查看公共列然后合并:

pd.merge(df1, df2, on=common_cols, how='inner')

编辑: 新问题(cmets),已经从 df2 中识别出也存在于第一个数据帧(df1)中的行,是否可以获取 pd.merge() 的结果然后从 df2 中删除也存在于 df1 中的行

我不知道有一种直接的方法来完成从 df2 中删除也存在于 df1 中的行的任务。也就是说,您可以使用以下内容:

ds1 = set(tuple(line) for line in df1.values)
ds2 = set(tuple(line) for line in df2.values)
df = pd.DataFrame(list(ds2.difference(ds1)), columns=df2.columns)

可能存在更好的方法来完成该任务,但我不知道这样的方法/功能。

编辑 2:如何从 df2 中删除也存在于 df1 中的行,如@WR 答案所示。

df2[~df2['A'].isin(df12['A'])] 提供的方法并不适用于所有类型的情况。考虑以下 DataFrame:

df1:

   A  B  C  D
0  6  4  1  6
1  7  6  6  8
2  1  6  2  7
3  8  0  4  1
4  1  0  2  3
5  8  4  7  5
6  4  7  1  1
7  3  7  3  4
8  5  2  8  8
9  3  2  8  4

df2:

   A  B  C  D
0  1  0  2  3
1  8  4  7  5
2  4  7  1  1
3  3  7  3  4
4  5  2  8  8
5  1  1  1  1
6  2  2  2  2

df12:

   A  B  C  D
0  1  0  2  3
1  8  4  7  5
2  4  7  1  1
3  3  7  3  4
4  5  2  8  8

使用上述 DataFrames 以从 df2 中删除也存在于 df1 中的行将导致以下结果:

   A  B  C  D
0  1  1  1  1
1  2  2  2  2

行 (1, 1, 1, 1) 和 (2, 2, 2, 2) 在 df2 中,而不在 df1 中。不幸的是,使用提供的方法 (df2[~df2['A'].isin(df12['A'])]) 会导致:

   A  B  C  D
6  2  2  2  2

这是因为 A 列中 1 的值在交集 DataFrame(即 (1, 0, 2, 3))和 df2 中都找到,因此同时删除了 (1, 0, 2, 3) 和 (1 , 1, 1, 1)。这是无意的,因为行 (1, 1, 1, 1) 不在 df1 中,不应删除。

我认为以下将提供解决方案。它创建了一个虚拟列,稍后用于将 DataFrame 子集化为所需的结果:

df12['key'] = 'x'
temp_df = pd.merge(df2, df12, on=df2.columns.tolist(), how='left')
temp_df[temp_df['key'].isnull()].drop('key', axis=1)

【讨论】:

哦,当然! SQL INNER JOIN。那逃脱了我。一个问题是我从未将它用于JOINing ON 多个列。如果要检查所有数据框的列,您能否将 on=['A', 'B', 'C', 'D'] 替换为 on=df1.columns 您可以使用on=list(df1.columns) 或等效的on=list(df2.columns)。如果要检查行是否相同(所有列),df1 和 df2 中的列必须相同。 安德鲁,最后一个问题(我也将它添加到原始帖子中) - 已经确定了来自df2 的行也存在于第一个数据帧(df1)中,是否有可能获取pd.merge() 的结果,然后从df2 中删除df1 中也存在的行? 在 EDIT 2 中,现在您似乎拥有两个起始数据框,分别为 df12df2。你的意思是df2['key'] = 'x'temp_df = pd.merge(df2, df1, on=df2.col......。我 从上面的例子中,pd.merge() 应该生成 max(len(df1), len(df2)) 中的项目数。当我执行pd.merge(df1, df2, on=df1.columns.tolist()[:-1], how='inner') 时,我得到的输出是&gt; max(len(df1), len(df2))?我错过了什么?【参考方案2】:

@Andrew:我相信我找到了一种方法来删除一个数据帧中已经存在于另一个数据帧中的行(即回答我的编辑)而不使用循环 - 如果您不同意和/或我的 OP + EDIT,请告诉我没有明确说明这一点:

这行得通

两个数据框的列始终相同 - ABCD。考虑到这一点,在很大程度上基于 Andrew 的方法,以下是如何从 df2 中删除也存在于 df1 中的行:

common_cols = df1.columns.tolist()                         #generate list of column names
df12 = pd.merge(df1, df2, on=common_cols, how='inner')     #extract common rows with merge
df2 = df2[~df2['A'].isin(df12['A'])]

第 3 行执行以下操作:

仅从df2 中提取与df1 中的行不匹配的行: 为了使 2 行不同,一行中的任何一列都必须 必然不同于另一列中的对应列 行。 在这里,我选择了A 列进行比较 - 它是 可以使用任何列名,但不是所有 列名。

注意:此方法本质上等同于 SQL NOT IN()

【讨论】:

你无法想象我浪费了多少时间尝试使用循环来完成这项工作。 我认为您的逻辑可能有问题(尽管我可能误解了您想要的结果)我已经相应地更新了我的答案。 你是对的。你的是一个更好的解决方案。感谢您向我指出这一点。 这个command中的'~'操作符是什么意思? df2 = df2[~df2['A'].isin(df12['A'])] 这意味着来自df2 的列A不是在来自df12A 列中。

以上是关于比较 Python Pandas DataFrames 以匹配行的主要内容,如果未能解决你的问题,请参考以下文章

pandas一些基本操作(DataFram和Series)_4

pandas一些基本操作(DataFram和Series)_2

Datafram 实现作为正文发送邮件

Python数据分析之Pandas

Python数据分析之Pandas

Python Pandas 数据框创建