`pd.concat` 与 `join=='inner'` 不会产生 pandas 数据帧的交集

Posted

技术标签:

【中文标题】`pd.concat` 与 `join==\'inner\'` 不会产生 pandas 数据帧的交集【英文标题】:`pd.concat` with `join=='inner'` doesn't produce intersection of pandas dataframes`pd.concat` 与 `join=='inner'` 不会产生 pandas 数据帧的交集 【发布时间】:2021-04-04 23:21:53 【问题描述】:

我正在尝试使用 pd.concat 从多个数据帧中提取公共行:

>>> import numpy as np
>>> import pandas as pd
>>> x = np.random.random(size=(5, 3))
>>> df1 = pd.DataFrame(x)
>>> df2 = pd.DataFrame(x[1:3])
>>> df3 = pd.DataFrame(x[2:4])
>>> df1
          0         1         2
0  0.257662  0.453542  0.805230
1  0.060493  0.463148  0.715994
2  0.452379  0.470137  0.965709
3  0.447546  0.964252  0.163247
4  0.187212  0.973557  0.871090
>>> df2
          0         1         2
0  0.060493  0.463148  0.715994
1  0.452379  0.470137  0.965709
>>> df3
          0         1         2
0  0.452379  0.470137  0.965709
1  0.447546  0.964252  0.163247

如您所见,只有行 0.452379 0.470137 0.965709 是所有三个数据帧共有的。为了提取它,我尝试了:

>>> pd.concat([df1, df2, df3], join='inner')
          0         1         2
0  0.257662  0.453542  0.805230
1  0.060493  0.463148  0.715994
2  0.452379  0.470137  0.965709
3  0.447546  0.964252  0.163247
4  0.187212  0.973557  0.871090
0  0.060493  0.463148  0.715994
1  0.452379  0.470137  0.965709
0  0.452379  0.470137  0.965709
1  0.447546  0.964252  0.163247

因此,join==inner 似乎不起作用!我还应该指出 ignore_index=True 对行为没有影响。在 Real Python 上的 an article 中,建议使用 axis=1。但是,我认为这是错误的:

>>> pd.concat([df1, df2, df3], join='inner', axis=1)
          0         1         2         0         1         2         0         1         2
0  0.257662  0.453542  0.805230  0.060493  0.463148  0.715994  0.452379  0.470137  0.965709
1  0.060493  0.463148  0.715994  0.452379  0.470137  0.965709  0.447546  0.964252  0.163247

我所做的有什么问题?另外,如果这种方式不起作用,我将如何从多个数据框中提取公共行?我正在使用 Pandas 0.25.3 版。

【问题讨论】:

想要的输出是什么? @U11-Forward :只有公共行的数据框。 【参考方案1】:

与@Ajay A 所说的类似,

import numpy as np
import pandas as pd
x = np.random.random(size=(5, 3))
df1 = pd.DataFrame(x)
df2 = pd.DataFrame(x[1:3])
df3 = pd.DataFrame(x[2:4])

那么,

df1
Out[22]: 
          0         1         2
0  0.845894  0.530659  0.629198
1  0.697229  0.225557  0.314540
2  0.972633  0.685077  0.191109
3  0.069966  0.961317  0.352933
4  0.176633  0.663602  0.235032

df2
Out[23]: 
          0         1         2
0  0.697229  0.225557  0.314540
1  0.972633  0.685077  0.191109

df3
Out[24]: 
          0         1         2
0  0.972633  0.685077  0.191109
1  0.069966  0.961317  0.352933

然后你可以使用pd.mergehow='inner'

pd.merge(df2, df3, how='inner')
Out[25]: 
          0         1         2
0  0.972633  0.685077  0.191109

或者如果你正在寻找的是三者的交集,

pd.merge(pd.merge(df1,df2,how='inner'), df3, how='inner')
Out[26]: 
          0         1         2
0  0.972633  0.685077  0.191109

使用for loop 处理df_list

df_list = [df1, df2, df3]
df_intersection = df1
for df in df_list[1:]:
    df_intersection = pd.merge(df_intersection, df, how='inner') 

【讨论】:

我不能接受使用 pd.merge 的解决方案。我知道这很好用,但我的问题是我事先不知道要在哪个交集上执行多少个数据帧。这就是我使用 pd.concat 接受数据帧列表的原因。 @Peaceful 也许你可以使用for loop 来完成这项工作:df_list = [df1, df2, df3]; df_intersection = df1; for df in df_list[1:]: df_intersection = pd.merge(df_intersection, df, how='inner') @Ferris:谢谢!这实际上是一个聪明的解决方法。也许你应该写这个作为答案。【参考方案2】:

简而言之,选择reduce(lambda left,right: pd.merge(left,right,on=cols), dfs), (参见方法#2 - 确保包含from functools import reduce),但请参见pd.concat 的解释(方法#1):

方法#1 (concat):我认为最动态、最强大的pd.concat 方式(在我专门尝试过concat 的方式中)是使用。此解决方案相对于下面第二种方法的唯一主要好处是您不必使用额外的库;但是,我认为您也可以使用 merge 编写类似的代码,而无需使用其他库:

dfs = [df1, df2, df3]
cols = [*df1.columns]                              # enclosing with [*] is the same as tolist()
for df in dfs:
    df.set_index(cols, inplace=True)               # can only use inplace when looping through dfs (at least using my simpler method)
pd.concat(dfs, join='inner', axis=1).reset_index() # see below paragraph for explanation
Out[1]: 
          0         1         2
0  0.452379  0.470137  0.965709

请注意join='inner' 表示您加入的是index 而不是唯一的行。此外,join 仅在您通过 axis=1 时才重要,这就是实际上什么都没有发生的原因。


方法#2:(merge with reduce):

@Anky 指出how='inner' 是默认的merge。这实际上是我发布的第一个答案,但我对预期的输出感到困惑并绕了一圈。请看下面最简单的答案:

from functools import reduce
dfs = [df1, df2, df3]
cols = [*df1.columns]
reduce(lambda left,right: pd.merge(left,right,on=cols), dfs)
Out[2]: 
          0         1         2
0  0.452379  0.470137  0.965709

【讨论】:

我可以试试这个,但这看起来很复杂。我的简单问题是“为什么 pd.concat with join='inner' 不起作用?”。 @Peaceful 有两个原因。请参阅我更新的答案的第一段。我还将向您展示如何按照自己的方式获得所需的输出。 @Peaceful 我想我已经提供了您正在寻找的两种优雅方法。如果你想用 concat 来做,那么你必须有一个“通用”索引。节日快乐! 感谢@anky 提到默认值的好点。我会更新我的答案并感谢你:)【参考方案3】:
# add extral tag column
df_list = [df1, df2, df3]
for i, dfi in enumerate(df_list):
    dfi['tag'] = i + 1

# merge DataFrame
df = pd.concat([df1, df2, df3], ignore_index=True)

# find the duplicates rows
cols = df.columns[:-1].tolist()
cond = df[cols].duplicated(keep=False)
obj = df[cond].groupby(cols)['tag'].agg(tuple)

# filter 
cond = obj.map(len) == len(df_list)
obj[cond]

obj 示例:

# 0         1         2       
# 0.148080  0.837398  0.565498       (1, 3)
# 0.572673  0.256735  0.620923    (1, 2, 3)
# 0.822542  0.856137  0.645639       (1, 2)
# Name: tag, dtype: object

【讨论】:

【参考方案4】:

如果您正在尝试查找公共行:

temp = pd.concat([df1, df2, df3])
temp[temp.duplicated()]

不过,我确信有一个更优雅的解决方案。

【讨论】:

我可能误解了预期的输出,但我不认为这是正确的。我认为预期的输出是一行:0 0.972633 0.685077 0.191109【参考方案5】:

试试这个,

df = pd.merge(df1, df2, how='inner', on=[col1, col2, col3])

【讨论】:

我知道这个,但我不想要这个。为此,我必须提前知道要合并多少帧。我想传递一个数据框列表。

以上是关于`pd.concat` 与 `join=='inner'` 不会产生 pandas 数据帧的交集的主要内容,如果未能解决你的问题,请参考以下文章

Python数据分析库pandas ------ mergeconcatenation pd.concat合并与拼接

一次性彻底讲透 Python 中 pd.concat 与 pd.merge

pandas使用pd.concat横向合并多个dataframe实战:多个dataframe的横向表拼接(行对齐)多个dataframe的横向表拼接(指定join参数交集还是并集)

pandas(11):数据合并

PANDAS 数据合并与重塑(concat篇)

pandas的concat方法