Python 3.x Python3熊猫-如何合并包含多行且具有相同键的两个数据帧

Python 3.x Python3熊猫-如何合并包含多行且具有相同键的两个数据帧,python-3.x,pandas,dataframe,merge,inner-join,Python 3.x,Pandas,Dataframe,Merge,Inner Join,我试图通过匹配的键合并两个数据帧,但键可能在每个数据帧中出现多次(n)。内部联接为所有n^2键对提供行-相反,我想要n行 对于某些上下文:想象一个图书馆的签入/签出场景,其中一本书可能被签入,记录在df1中,也可能被签出,并记录在df2中。每本书都有一个唯一的密钥,但可以多次签入/签出。此外,由于数据集仅跨越特定的时间窗口,某些书籍可能有签入记录但没有签出(在记录数据之前已签出的书籍),或者有签出记录但没有签入(尚未归还的书籍)。我的目标是创建一个新的数据框,它只包含同时存在签入和相应签出的行

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

对于某些上下文:想象一个图书馆的签入/签出场景,其中一本书可能被签入,记录在df1中,也可能被签出,并记录在df2中。每本书都有一个唯一的密钥,但可以多次签入/签出。此外,由于数据集仅跨越特定的时间窗口,某些书籍可能有签入记录但没有签出(在记录数据之前已签出的书籍),或者有签出记录但没有签入(尚未归还的书籍)。我的目标是创建一个新的数据框,它只包含同时存在签入和相应签出的行

最后,我想问一下我的问题:

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)

注意,对于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天)

我在df2的第0天添加了一个退房。现在在df中,我得到6个A2条目(一个A3条目),而我只想要2个A2条目(一个A3条目)。这将产生与上述相同的
df_

注意:发布的答案将匹配第1,2天的入住和第0,3天的退房,而不是第3,5天的退房。因此,完整的解决方案将需要确保结帐日期>=入住日期,或在最早入住时启动柜台,或类似的事情

我所尝试的: 我尝试实现了df.drop_duplicates()
的各种组合,但最终得到了不正确的组合。我还尝试手动执行此操作,方法是循环遍历两个数据集共用的所有ID(
common=set(df1.ID.values)&set(df2.ID.values)
),按出现顺序将它们配对,然后逐个将它们添加到新的df,但这似乎效率很低

这似乎是一个很常见的任务,可能有一个更“pythonic”的方法来处理这个问题

感谢您抽出时间,我非常感谢您的指导或提示。

以下是我的解决方案:

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
计数器。这是我们将“首次签入”与“首次签出”匹配的地方

现在我们可以在
id
计数器上执行
内部联接

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

我希望这有帮助

请将代码作为实际的代码块而不是屏幕截图发布-这确实有助于更多的人理解问题,并通过让他们可以复制/粘贴到他们的环境中来帮助解决问题。您可以使用R软件中的merge()函数来实现这一点。您可以编写csv文件并使用python读取它。我只是给你一个最快的解决方案。你说“最后,如果这在SQL等中更容易的话”-这可能是-但是不要忘记我们看不到你尝试过的尝试,你使用pandas等的所有结果。。。同样,你需要帮助我们来帮助你。谢谢你的建议@Jon,我还是个新手。我将编辑这篇文章,以包含代码块和我尝试的结果。所以我在下面回答了,但根据你描述的“完整案例”,我不是100%的,它涵盖了这一点。我建议你在可复制的数据集中放一个这样的例子。谢谢,@matt,这真的很有帮助!我没有想到使用groupby和cumsum来获得合并的唯一密钥。所以,只要我在第一次签入之前没有签出,这应该可以完美地工作!没问题!如果它最终成功了,请接受答案。
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