Python3 Pandas - 如何合并包含多个具有相同键

时间:2018-02-03 18:01:21

标签: python-3.x pandas dataframe merge inner-join

我尝试通过匹配键合并两个DataFrame,但每个DataFrame中的键可能会出现多次( n )次。内部联接为所有 n ^ 2 对键提供行 - 相反,我想要 n 行。

对于某些上下文:想象一下图书馆的签入/签出方案,其中可以签入书籍,记录在df1中,或签出,并记录在df2中。每本书都有一个唯一的密钥,但可以多次登记/退房。此外,由于数据集仅跨越特定时间窗口,因此某些书籍可能具有签到记录但不能签出(在记录数据之前已经签出的书籍),或者签出记录但不是办理登机手续(尚未归还的书籍)。我的目标是创建一个新的DataFrame,其中只包含签到和相应签出的行。

所以,最后得到我的问题:

1)如何进行内部联接,首次登记与首次结账,第二次结账时再次结账等?默认情况下,它会提供所有组合 - 因此,如果有 n 签到和 n 签出,我会获得 n ^ 2 行我想要的 n 行。(现在,让我们忽略在第一次办理登机手续之前退房的可能性,或者不等数量的签到/检查 - 以下是一个简单的例子。

df1 = pd.DataFrame({'ID': ['A1', 'A2','A2', 'A3'], 'DATE': [1, 1,2, 2]})
df2 = pd.DataFrame({'ID': ['A2', 'A3', 'A2', 'A4'], 'DATE': [3, 5, 5, 7]})
df = pd.merge(df1, df2, how='inner', on='ID',sort=True)

example script

请注意,我得到A2的4个条目和A3的1个条目,而我只想要A2的行0和2以及A3的行4。

df_wanted = pd.DataFrame({'ID': ['A2', 'A2', 'A3'], 'DATE_x': [1, 2, 2], 'DATE_y': [3, 5, 5]})

2)完整的案例。每张登记入住和退房手续均应配对,每张退房手续均与之前最近的登记入住。因此,如果在第1天和第2天办理登机手续,并在第0天,第3天和第5天办理登机手续,则最后df中的唯一行应对应于后两个签到(第1,2天)和第二个退房(第3,5天)。

df1 = pd.DataFrame({'ID': ['A1', 'A2','A2', 'A3'], 'DATE': [1, 1, 2, 2]})
df2 = pd.DataFrame({'ID': ['A2', 'A3', 'A2', 'A4','A2'], 'DATE': [3, 5, 5, 7, 0]})
df = pd.merge(df1, df2, how='inner', on='ID',sort=True)

我在第0天向df2添加了退房。现在在df中,我获得了A2的6个条目(以及A3的一个条目),而我只想要A2的两个条目(以及A3的一个条目)。这应该与上面的df_wanted相同。

注意:发布的答案将匹配从第1,2天到第0天结账的签到,而不是第3,5天的结账。因此,完整的解决方案需要确保结账日期> =办理登机手续的日期,或者在最早办理登机手续时启动柜台,或类似的东西。

我尝试了什么: 我尝试实现df.drop_duplicates()的各种组合,但我最终得到了错误的组合。我也尝试通过循环遍历两个数据集(common=set(df1.ID.values) & set(df2.ID.values))共有的所有ID,按照它们的出现顺序对它们进行配对,然后逐个将它们添加到新的df中来手动执行此操作,但是这样做看起来效率很低。

这似乎是一个足够普遍的任务,可能有更“pythonic”的方式来处理它?<​​/ p>

感谢您抽出宝贵时间,感谢您提出任何指导或建议。

1 个答案:

答案 0 :(得分:2)

这是我的解决方案:

import pandas as pd
df1 = pd.DataFrame({'date':[1, 1, 2, 2], 'id':['A1', 'A2', 'A2', 'A3']})
df2 = pd.DataFrame({'date':[3, 5, 5, 7], 'id':['A2', 'A3', 'A2', 'A4']})

df1 = df1[df1.id.isin(df2.id)]
df2 = df2[df2.id.isin(df1.id)]

df1['ones'] = 1
df1['counter'] = df1.groupby('id')['ones'].cumsum()
del df1['ones']

df2['ones'] = 1
df2['counter'] = df2.groupby('id')['ones'].cumsum()
del df2['ones']


df3 = pd.merge(df1, df2, on=['id', 'counter'], suffixes = ['_checkin', '_checkout'])
del df3['counter']

print(df3)

   date_checkin  id  date_checkout
0             1  A2              3
1             2  A2              5
2             2  A3              5

<强>步骤:

初始化数据帧:

import pandas as pd
df1 = pd.DataFrame({'date':[1, 1, 2, 2], 'id':['A1', 'A2', 'A2', 'A3']})
df2 = pd.DataFrame({'date':[3, 5, 5, 7], 'id':['A2', 'A3', 'A2', 'A4']})

仅按id

过滤掉彼此存在的记录的数据帧
df1 = df1[df1.id.isin(df2.id)]
df2 = df2[df2.id.isin(df1.id)]

创建一个cumsum计数器以匹配。这是我们将在#34;第一次检查&#34;用&#34;首先结帐&#34;

df1['ones'] = 1
df1['counter'] = df1.groupby('id')['ones'].cumsum()
del df1['ones']

df2['ones'] = 1
df2['counter'] = df2.groupby('id')['ones'].cumsum()
del df2['ones']

现在我们可以对inner joinid

进行counter
df3 = pd.merge(df1, df2, on=['id', 'counter'], suffixes = ['_checkin', '_checkout'])
del df3['counter']

print(df3)

   date_checkin  id  date_checkout
0             1  A2              3
1             2  A2              5
2             2  A3              5

我希望这有帮助!