如何合并两个数据框(DataFrame)而不包含重复项?

112
我想将两个数据框A和B连接成一个新的数据框,但不包含重复的行(如果B中的行已经存在于A中,则不添加)。
数据框A:
   I    II   
0  1    2    
1  3    1    

数据框 B:
   I    II
0  5    6
1  3    1

新数据框:
   I    II
0  1    2
1  3    1
2  5    6

怎么做呢?

请确保您的示例是有效的Python代码,并且没有遗漏任何内容。看起来您缺少一个索引。 - TomAugspurger
A和B是数据框的名称。这是你的意思吗? - MJP
我的意思是类似于 A = pd.DataFrame({"II": [2, 1]}, index=[1, 3])I列是DataFrame的索引吗? - TomAugspurger
我和II是指列名。 - MJP
1
然后你应该在你的示例中包含索引。最好从提示中复制粘贴一个示例。 - TomAugspurger
4个回答

166

最简单的方法是将它们连接在一起,然后去重。

>>> df1
   A  B
0  1  2
1  3  1
>>> df2
   A  B
0  5  6
1  3  1
>>> pandas.concat([df1,df2]).drop_duplicates().reset_index(drop=True)
   A  B
0  1  2
1  3  1
2  5  6

reset_index(drop=True) 的作用是在 concat()drop_duplicates() 后重置索引。如果不这样做,你将得到一个索引为 [0,1,0] 而不是 [0,1,2] 的数据框(dataframe),如果不立即重置索引,则可能会���该 dataframe 以后的进一步操作造成问题。


5
在concat中可以使用ignore_index=True来避免重复索引。 - Andy Hayden
5
@AndyHayden 也许值得注意的是,你可以使用 ignore_index=True 来避免重复索引,但如果你不使用 reset_index,那么你可能会跳过一些索引(因为它们被删除了)(例如 0、1、2、4、5…),这可能并不理想。 - KRish
reset_index会丢失索引信息。当我们需要索引信息时,如果索引不使用数字,则会出现问题。 - Niuya

5
如果DataFrame A中已经有重复行,则将它们连接起来并删除重复行,可能会删除你想保留的行。
在这种情况下,你需要创建一个具有累计计数的新列,然后去除重复项。这取决于你的使用情况,但这在时间序列数据中很常见。
以下是一个例子:
df_1 = pd.DataFrame([
{'date':'11/20/2015', 'id':4, 'value':24},
{'date':'11/20/2015', 'id':4, 'value':24},
{'date':'11/20/2015', 'id':6, 'value':34},])

df_2 = pd.DataFrame([
{'date':'11/20/2015', 'id':4, 'value':24},
{'date':'11/20/2015', 'id':6, 'value':14},
])


df_1['count'] = df_1.groupby(['date','id','value']).cumcount()
df_2['count'] = df_2.groupby(['date','id','value']).cumcount()

df_tot = pd.concat([df_1,df_2], ignore_index=False)
df_tot = df_tot.drop_duplicates()
df_tot = df_tot.drop(['count'], axis=1)
>>> df_tot

date    id  value
0   11/20/2015  4   24
1   11/20/2015  4   24
2   11/20/2015  6   34
1   11/20/2015  6   14

4

我很惊讶pandas没有提供本地解决方案来完成这个任务。如果你处理大型数据集(如Rian G建议的),仅仅删除重复项并不高效。

使用集合查找非重叠索引可能是最有效的方法。然后使用列表推导将索引转换为“行位置”(布尔值),您需要使用iloc[,]访问行。下面是一个执行此任务的函数。如果您没有选择要检查重复项的特定列(col),则将使用索引,正如您所请求的那样。如果您选择了特定列,请注意'a'中现有的重复条目将保留在结果中。

import pandas as pd

def append_non_duplicates(a, b, col=None):
    if ((a is not None and type(a) is not pd.core.frame.DataFrame) or (b is not None and type(b) is not pd.core.frame.DataFrame)):
        raise ValueError('a and b must be of type pandas.core.frame.DataFrame.')
    if (a is None):
        return(b)
    if (b is None):
        return(a)
    if(col is not None):
        aind = a.iloc[:,col].values
        bind = b.iloc[:,col].values
    else:
        aind = a.index.values
        bind = b.index.values
    take_rows = list(set(bind)-set(aind))
    take_rows = [i in take_rows for i in bind]
    return(pd.concat([a, b.iloc[take_rows,:]]))

# Usage
a = pd.DataFrame([[1,2,3],[1,5,6],[1,12,13]], index=[1000,2000,5000])
b = pd.DataFrame([[1,2,3],[4,5,6],[7,8,9]], index=[1000,2000,3000])

append_non_duplicates(a,b)
#        0   1   2
# 1000   1   2   3    <- from a
# 2000   1   5   6    <- from a
# 5000   1  12  13    <- from a
# 3000   7   8   9    <- from b

append_non_duplicates(a,b,0)
#       0   1   2
# 1000  1   2   3    <- from a
# 2000  1   5   6    <- from a
# 5000  1  12  13    <- from a
# 2000  4   5   6    <- from b
# 3000  7   8   9    <- from b

如果只需要删除所有行值都重复的行怎么办?在示例中使用 col = 0 将删除 b 中以 1 开头的每一行。 - ns63sr
1
通常情况下,我们使用 isinstance 而不是 type(...) is ... - Winand

2

另一个选择:

concatenation = pd.concat([
    dfA,
    dfB[dfB['I'].isin(dfA['I']) == False], # <-- get all the data in dfB that doesn't show up in dfB (based on values in column 'I')
])

对象concatenation将是:

     I    II
  0  1    2
  1  3    1
  2  5    6

1
这是对我起作用的。我担心drop_dupes会删除错误的副本。这里的意图非常清楚易读。一个建议- flake8标记== False,而更喜欢is False,然而is False会引发KeyError(我猜测因为我们正在处理bools的系列)。我采用的语法是~,它表示not,即dfB[~dfB['I'].isin(dfA['I'])] - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接