Pandas合并入门

914
  • 如何使用pandas执行(INNER| (LEFT|RIGHT|FULL) OUTER) JOIN
  • 在合并后如何为缺失的行添加NaN?
  • 如何在合并后去掉NaN?
  • 我可以在索引上合并吗?
  • 如何合并多个DataFrames?
  • 如何使用pandas进行交叉连接?
  • mergejoinconcatupdate?谁?什么?为什么?!

...还有更多。我看到这些反复出现的问题询问关于pandas合并功能的各种方面。今天关于合并及其各种用例的大部分信息都分散在数十篇措辞不当、难以搜索的帖子中。这里的目的是为了将一些更重要的要点整理出来,供后人参考。

本问答旨在成为常见pandas习语系列用户指南的下一个安装程序(请参阅this post on pivotingthis post on concatenation,稍后我将涉及这些内容)。

请注意,本文并不意味着要取代文档,因此请也阅读文档!其中一些示例来自于文档。


目录

为了方便访问。

8个回答

1247

本文旨在为读者提供有关使用Pandas进行SQL风格合并的基础知识,以及如何使用它以及何时不要使用它。

具体而言,本文将介绍以下内容:

  • 基础知识 - 合并类型(LEFT、RIGHT、OUTER、INNER)

    • 使用不同列名进行合并
    • 使用多个列进行合并
    • 避免输出中出现重复的合并键列

本文(以及我在此主题上发布的其他帖子)不会涉及以下内容:

  • 性能相关的讨论和时间表(目前为止)。在适当的情况下,大多数更好的替代方案都会被提到。
  • 处理后缀、删除额外列、重命名输出和其他特定用例。有其他(更好的)帖子来处理这些问题,所以自己解决吧!

注意 大多数示例默认使用INNER JOIN操作来演示各种功能,除非另有说明。

此外,这里的所有数据框都可以复制和复制,以便您可以使用它们。此外,请参见此帖子,了解如何从剪贴板读取数据框。

最后,所有JOIN操作的视觉表示都是使用Google Drawings手绘的。灵感来自这里



不要多说 - 直接展示如何使用merge

设置和基础知识

np.random.seed(0)
left = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'value': np.random.randn(4)})
right = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'value': np.random.randn(4)})

left

  key     value
0   A  1.764052
1   B  0.400157
2   C  0.978738
3   D  2.240893

right

  key     value
0   B  1.867558
1   D -0.977278
2   E  0.950088
3   F -0.151357

为简化起见,关键列现在使用相同的名称。

INNER JOIN 由以下表示

注意:这里以及接下来的所有内容都遵循以下约定:
  • 蓝色表示合并结果中存在的行
  • 红色表示从结果中排除的行(即已删除)
  • 绿色表示在结果中被替换为NaN的缺失值
要执行内连接,请在左侧DataFrame上调用merge,将右侧DataFrame和连接键(至少)作为参数指定。
left.merge(right, on='key')
# Or, if you want to be explicit
# left.merge(right, on='key', how='inner')

  key   value_x   value_y
0   B  0.400157  1.867558
1   D  2.240893 -0.977278

这将仅返回具有共同键(在此示例中为“B”和“D”)的leftright中的行。

LEFT OUTER JOIN或LEFT JOIN由以下表示:

可以通过指定how='left'来执行此操作。

left.merge(right, on='key', how='left')

  key   value_x   value_y
0   A  1.764052       NaN
1   B  0.400157  1.867558
2   C  0.978738       NaN
3   D  2.240893 -0.977278

请注意这里NaN的位置。如果您指定how='left',则仅使用来自left的键,并且从right缺失的数据将被替换为NaN。

同样地,对于RIGHT OUTER JOIN或RIGHT JOIN,它是...

...指定 how='right'

left.merge(right, on='key', how='right')

  key   value_x   value_y
0   B  0.400157  1.867558
1   D  2.240893 -0.977278
2   E       NaN  0.950088
3   F       NaN -0.151357

这里使用了来自right的键,缺失的数据将被NaN替换。最后,对于FULL OUTER JOIN,给出如下:

指定how='outer'

left.merge(right, on='key', how='outer')

  key   value_x   value_y
0   A  1.764052       NaN
1   B  0.400157  1.867558
2   C  0.978738       NaN
3   D  2.240893 -0.977278
4   E       NaN  0.950088
5   F       NaN -0.151357

这将使用两个框架中的键,并在两者中缺少行的位置插入NaN。

文档很好地总结了这些不同的合并方式:

Enter image description here


其他JOIN - 左排斥、右排斥和全排斥/反向连接

如果需要使用左排斥JOIN右排斥JOIN,需要进行两个步骤。

对于左排斥JOIN,表示为

首先执行左外连接,然后过滤只来自left的行(排除右侧的所有内容),

(left.merge(right, on='key', how='left', indicator=True)
     .query('_merge == "left_only"')
     .drop('_merge', 1))

  key   value_x  value_y
0   A  1.764052      NaN
2   C  0.978738      NaN

在哪里,

left.merge(right, on='key', how='left', indicator=True)

  key   value_x   value_y     _merge
0   A  1.764052       NaN  left_only
1   B  0.400157  1.867558       both
2   C  0.978738       NaN  left_only
3   D  2.240893 -0.977278       both

同样地,对于RIGHT-Excluding JOIN:

(left.merge(right, on='key', how='right', <b>indicator=True</b>)
     .query('_merge == "right_only"')
     .drop('_merge', 1))

  key  value_x   value_y
2   E      NaN  0.950088
3   F      NaN -0.151357

最后,如果你需要进行只保留左侧或右侧键的合并操作(即执行ANTI-JOIN),请注意。

您可以以类似的方式执行此操作——

(left.merge(right, on='key', how='outer', indicator=True)
     .query('_merge != "both"')
     .drop('_merge', 1))

  key   value_x   value_y
0   A  1.764052       NaN
2   C  0.978738       NaN
4   E       NaN  0.950088
5   F       NaN -0.151357

关键列的不同命名

如果关键列的命名不同,例如left使用keyLeft,而right使用keyRight而不是key,那么您需要将left_onright_on指定为参数,而不是on

left2 = left.rename({'key':'keyLeft'}, axis=1)
right2 = right.rename({'key':'keyRight'}, axis=1)

left2

  keyLeft     value
0       A  1.764052
1       B  0.400157
2       C  0.978738
3       D  2.240893

right2

  keyRight     value
0        B  1.867558
1        D -0.977278
2        E  0.950088
3        F -0.151357

left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')

  keyLeft   value_x keyRight   value_y
0       B  0.400157        B  1.867558
1       D  2.240893        D -0.977278

避免输出中的重复列

当从leftright中使用keyLeftkeyRight进行合并时,如果您只想在输出中包含一个 keyLeftkeyRight(但不是两者都包含),则可以首先将索引设置为预备步骤。

left3 = left2.set_index('keyLeft')
left3.merge(right2, left_index=True, right_on='keyRight')

    value_x keyRight   value_y
0  0.400157        B  1.867558
1  2.240893        D -0.977278

与刚才的命令输出相比(即left2.merge(right2, left_on='keyLeft', right_on='keyRight', how='inner')的输出),你会注意到keyLeft列缺失。你可以根据哪个数据框的索引设置为键来确定要保留哪一列。这在执行某些外连接操作时可能很重要。

从一个DataFrame中仅合并单个列

例如,考虑以下情况

right3 = right.assign(newcol=np.arange(len(right)))
right3
  key     value  newcol
0   B  1.867558       0
1   D -0.977278       1
2   E  0.950088       2
3   F -0.151357       3

如果您只需要合并“newcol”(没有其他列),通常可以在合并之前对列进行子集处理:

left.merge(right3[['key', 'newcol']], on='key')

  key     value  newcol
0   B  0.400157       0
1   D  2.240893       1

如果你正在进行左外连接,更高效的解决方案将涉及使用map

# left['newcol'] = left['key'].map(right3.set_index('key')['newcol']))
left.assign(newcol=left['key'].map(right3.set_index('key')['newcol']))

  key     value  newcol
0   A  1.764052     NaN
1   B  0.400157     0.0
2   C  0.978738     NaN
3   D  2.240893     1.0

如前所述,这与{{某个东西}}相似,但更快。

left.merge(right3[['key', 'newcol']], on='key', how='left')

  key     value  newcol
0   A  1.764052     NaN
1   B  0.400157     0.0
2   C  0.978738     NaN
3   D  2.240893     1.0

多列合并

若要在多个列上进行合并,请为on(或适当的left_onright_on)指定一个列表。

left.merge(right, on=['key1', 'key2'] ...)

或者,如果名称不同,

left.merge(right, left_on=['lkey1', 'lkey2'], right_on=['rkey1', 'rkey2'])

其他有用的merge*操作和函数

这一部分只涵盖最基础的内容,旨在激起你的兴趣。有关更多示例和案例,请参阅mergejoinconcat的文档以及功能规范链接。



继续阅读

跳转到Pandas合并101中的其他主题以继续学习:

*您在此处。


6
如果有人对每篇文章结尾的目录感到困惑,我将这篇庞大的答案分成了4个部分,其中3个在这个问题上,另一个在别处。之前的设置方式使得引用特定主题变得更加困难。现在,您可以轻松地将不同的主题添加书签! - cs95
5
这是一个很棒的资源!我唯一还有的问题是为什么称其为 merge 而不是 join,而称其为 join 而不是 merge? - ThatNewGuy

94

pd.concat([df0, df1], kwargs)的补充视图。请注意,kwarg axis=0或axis=1的含义并不像df.mean()或df.apply(func)那样直观。


on pd.concat([df0, df1])


13
这是一张不错的图表。请问您是如何制作它的? - cs95
9
谷歌文档内置的“插入 ==> 图形... ==> 新建”功能(截至2019年5月)。但需要明确的是:我之所以在谷歌文档中使用这张图片,仅仅是因为我的笔记储存在谷歌文档中,而我希望可以快速地在文档内修改这个图片。事实上,现在你提到它,谷歌文档的绘图工具确实很不错。 - eliu
4
“@Ufos Isn't that exactly what axis=1 and axis=0 is?” 的意思是“@Ufos 不就是 axis=1axis=0 吗?”axis=1表示沿着行的方向进行操作,axis=0则表示沿着列的方向进行操作。 - cs95
5
是的,现在有mergeconcat以及轴和其他内容。然而,正如@eliu所展示的,这只是合并的相同概念,具有“左”和“右”以及“水平”或“垂直”的概念。 就我个人而言,每次我必须记住哪个“axis”是0,哪个是1时,我都不得不查看文档。 - Ufos
2
如果可能的话,有人应该在.mean() .apply() .dropna() .concat()中解释axis=0axis=1。我必须仔细考虑每种情况。 - eliu
显示剩余3条评论

76

26
在这个答案中,我将考虑以下实际示例:
  1. pandas.concat

  2. 使用pandas.DataFrame.merge合并来自一个DataFrame的索引和另一个DataFrame的列。

我们将为每个情况使用不同的数据帧。

1. pandas.concat

考虑具有相同列名称的以下DataFrames:

  • Price2018,大小为(8784, 5)

  •    Year  Month  Day  Hour  Price
    0  2018      1    1     1   6.74
    1  2018      1    1     2   4.74
    2  2018      1    1     3   3.66
    3  2018      1    1     4   2.30
    4  2018      1    1     5   2.30
    5  2018      1    1     6   2.06
    6  2018      1    1     7   2.06
    7  2018      1    1     8   2.06
    8  2018      1    1     9   2.30
    9  2018      1    1    10   2.30
    
  • 价格2019,尺寸为(8760, 5)

  •    Year  Month  Day  Hour  Price
    0  2019      1    1     1  66.88
    1  2019      1    1     2  66.88
    2  2019      1    1     3  66.00
    3  2019      1    1     4  63.64
    4  2019      1    1     5  58.85
    5  2019      1    1     6  55.47
    6  2019      1    1     7  56.00
    7  2019      1    1     8  61.09
    8  2019      1    1     9  61.01
    9  2019      1    1    10  61.00
    

可以使用 pandas.concat 将它们组合起来,只需简单地

import pandas as pd

frames = [Price2018, Price2019]

df_merged = pd.concat(frames)

这将导致一个大小为(17544, 5)的DataFrame。

如果想要清楚地了解发生了什么,它是这样工作的:

How concat works

(来源)


2. pandas.DataFrame.merge

在本节中,我们将考虑一种特殊情况:合并一个dataframe的索引和另一个dataframe的列。

假设有一个包含54列的Geo数据帧,其中一列是Date,类型为datetime64[ns]

                 Date         1         2  ...        51        52        53
0 2010-01-01 00:00:00  0.565919  0.892376  ...  0.593049  0.775082  0.680621
1 2010-01-01 01:00:00  0.358960  0.531418  ...  0.734619  0.480450  0.926735
2 2010-01-01 02:00:00  0.531870  0.221768  ...  0.902369  0.027840  0.398864
3 2010-01-01 03:00:00  0.475463  0.245810  ...  0.306405  0.645762  0.541882
4 2010-01-01 04:00:00  0.954546  0.867960  ...  0.912257  0.039772  0.627696

数据框Price有一个名为Price的价格列,其索引对应日期(Date)

                     Price
Date                      
2010-01-01 00:00:00  29.10
2010-01-01 01:00:00   9.57
2010-01-01 02:00:00   0.00
2010-01-01 03:00:00   0.00
2010-01-01 04:00:00   0.00
为了合并它们,可以使用pandas.DataFrame.merge,方法如下:
df_merged = pd.merge(Price, Geo, left_index=True, right_on='Date')

其中GeoPrice是之前的数据帧。

这将导致以下数据帧:

where Geo and Price are the previous dataframes.

That results in the following dataframe

   Price                Date         1  ...        51        52        53
0  29.10 2010-01-01 00:00:00  0.565919  ...  0.593049  0.775082  0.680621
1   9.57 2010-01-01 01:00:00  0.358960  ...  0.734619  0.480450  0.926735
2   0.00 2010-01-01 02:00:00  0.531870  ...  0.902369  0.027840  0.398864
3   0.00 2010-01-01 03:00:00  0.475463  ...  0.306405  0.645762  0.541882
4   0.00 2010-01-01 04:00:00  0.954546  ...  0.912257  0.039772  0.627696

18
本文将涵盖以下主题:
  • 在不同条件下与索引合并
    • 基于索引的连接选项: mergejoinconcat
    • 在索引上合并
    • 在一个表格的索引和另一个表格的列上合并
  • 有效使用命名索引简化合并语法

返回顶部



基于索引的连接

简介

根据使用情况,有几种选择,其中一些更简单。

  1. DataFrame.mergeleft_indexright_index(或使用命名索引的left_onright_on
    • 支持内部/左侧/右侧/全连接
    • 每次只能连接两个对象
    • 支持列-列、索引-列、索引-索引连接
  2. DataFrame.join(基于索引连接)
    • 支持内部/左侧(默认)/右侧/全连接
    • 可以同时连接多个数据框
    • 支持索引-索引连接
  3. pd.concat(基于索引连接)
    • 支持内部/全连接(默认)
    • 可以同时连接多个数据框
    • 支持索引-索引连接

索引到索引的连接

设置和基础知识

import pandas as pd
import numpy as np

np.random.seed([3, 14])
left = pd.DataFrame(data={'value': np.random.randn(4)}, 
                    index=['A', 'B', 'C', 'D'])    
right = pd.DataFrame(data={'value': np.random.randn(4)},  
                     index=['B', 'D', 'E', 'F'])
left.index.name = right.index.name = 'idxkey'

left
           value
idxkey          
A      -0.602923
B      -0.402655
C       0.302329
D      -0.524349

right
 
           value
idxkey          
B       0.543843
D       0.013135
E      -0.326498
F       1.385076

通常,基于索引的内连接看起来像这样:

{{内连接 on 索引}}

left.merge(right, left_index=True, right_index=True)

         value_x   value_y
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

其他连接遵循类似的语法。

值得注意的替代方案

  1. DataFrame.join defaults to joins on the index. DataFrame.join does a LEFT OUTER JOIN by default, so how='inner' is necessary here.

     left.join(right, how='inner', lsuffix='_x', rsuffix='_y')
    
              value_x   value_y
     idxkey                    
     B      -0.402655  0.543843
     D      -0.524349  0.013135
    

    Note that I needed to specify the lsuffix and rsuffix arguments since join would otherwise error out:

     left.join(right)
     ValueError: columns overlap but no suffix specified: Index(['value'], dtype='object')
    

    Since the column names are the same. This would not be a problem if they were differently named.

     left.rename(columns={'value':'leftvalue'}).join(right, how='inner')
    
             leftvalue     value
     idxkey                     
     B       -0.402655  0.543843
     D       -0.524349  0.013135
    
  2. pd.concat joins on the index and can join two or more DataFrames at once. It does a full outer join by default, so how='inner' is required here..

     pd.concat([left, right], axis=1, sort=False, join='inner')
    
                value     value
     idxkey                    
     B      -0.402655  0.543843
     D      -0.524349  0.013135
    

    For more information on concat, see this post.


索引到列的连接

要使用左侧的索引和右侧的列执行内连接,您将使用DataFrame.merge的组合,其中包括left_index=Trueright_on=...

right2 = right.reset_index().rename({'idxkey' : 'colkey'}, axis=1)
right2
 
  colkey     value
0      B  0.543843
1      D  0.013135
2      E -0.326498
3      F  1.385076

left.merge(right2, left_index=True, right_on='colkey')

    value_x colkey   value_y
0 -0.402655      B  0.543843
1 -0.524349      D  0.013135

其他连接遵循类似的结构。请注意,只有merge可以执行索引到列的连接。您可以在多个列上进行连接,前提是左边的索引级别数量等于右边的列数。 joinconcat无法进行混合合并。您需要使用DataFrame.set_index将索引设置为预处理步骤。

有效使用命名索引[pandas >= 0.23]

如果您的索引已命名,则从pandas >= 0.23开始,DataFrame.merge允许您指定索引名称到on(或根据需要使用left_onright_on)。

left.merge(right, on='idxkey')

         value_x   value_y
idxkey                    
B      -0.402655  0.543843
D      -0.524349  0.013135

对于之前的左侧索引合并右侧列的示例,您可以使用left_on和左侧索引名称:

left.merge(right2, left_on='idxkey', right_on='colkey')

    value_x colkey   value_y
0 -0.402655      B  0.543843
1 -0.524349      D  0.013135


继续阅读

跳转到Pandas合并101中的其他主题以继续学习:

* 您在此处


16

本文将介绍以下主题:

  • 如何正确地推广到多个数据帧(以及为什么merge在这里存在缺陷)
  • 基于唯一键合并
  • 基于非唯一键合并

返回顶部



将多个数据帧推广到通用情况

经常出现这样的情况,需要将多个数据帧合并在一起。可以使用链式调用merge来完成:

df1.merge(df2, ...).merge(df3, ...)

然而,对于许多数据框来说,这很快就会失控。此外,有时需要为未知数量的数据框进行泛化处理。

在这里,我介绍了pd.concat用于基于“唯一”键的多路连接和DataFrame.join用于基于“非唯一”键的多路连接。首先,设置。

# Setup.
np.random.seed(0)
A = pd.DataFrame({'key': ['A', 'B', 'C', 'D'], 'valueA': np.random.randn(4)})    
B = pd.DataFrame({'key': ['B', 'D', 'E', 'F'], 'valueB': np.random.randn(4)})
C = pd.DataFrame({'key': ['D', 'E', 'J', 'C'], 'valueC': np.ones(4)})
dfs = [A, B, C] 

# Note: the "key" column values are unique, so the index is unique.
A2 = A.set_index('key')
B2 = B.set_index('key')
C2 = C.set_index('key')

dfs2 = [A2, B2, C2]

基于唯一键的多路合并

如果您的键(此处,键可以是列或索引)是唯一的,则可以使用pd.concat。请注意,pd.concat在索引上连接DataFrames

# Merge on `key` column. You'll need to set the index before concatenating
pd.concat(
    [df.set_index('key') for df in dfs], axis=1, join='inner'
).reset_index()

  key    valueA    valueB  valueC
0   D  2.240893 -0.977278     1.0

# Merge on `key` index.
pd.concat(dfs2, axis=1, sort=False, join='inner')

       valueA    valueB  valueC
key                            
D    2.240893 -0.977278     1.0

对于 FULL OUTER JOIN,请省略 join='inner'。请注意,您无法指定 LEFT 或 RIGHT OUTER JOIN(如果需要这些,请使用下面描述的join)。


带有重复键的多路合并

concat很快,但它有其缺点。它无法处理重复项。

A3 = pd.DataFrame({'key': ['A', 'B', 'C', 'D', 'D'], 'valueA': np.random.randn(5)})
pd.concat([df.set_index('key') for df in [A3, B, C]], axis=1, join='inner')
ValueError: Shape of passed values is (3, 4), indices imply (3, 2)

在这种情况下,我们可以使用join,因为它可以处理非唯一键(请注意,join会在它们的索引上连接DataFrames;它在底层调用merge并执行左外连接,除非另有规定)。

# Join on `key` column. Set as the index first.
# For inner join. For left join, omit the "how" argument.
A.set_index('key').join([B2, C2], how='inner').reset_index()

  key    valueA    valueB  valueC
0   D  2.240893 -0.977278     1.0

# Join on `key` index.
A3.set_index('key').join([B2, C2], how='inner')

       valueA    valueB  valueC
key                            
D    1.454274 -0.977278     1.0
D    0.761038 -0.977278     1.0

继续阅读

跳转到Pandas合并101中的其他主题以继续学习:

* 您在此处


2
Pandas目前不支持在合并语法中进行不等式连接;其中一种选择是使用pyjanitor中的conditional_join函数 - 我是这个库的贡献者。
# pip install pyjanitor
import pandas as pd
import janitor 

left.conditional_join(right, ('value', 'value', '>'))

   left           right
    key     value   key     value
0     A  1.764052     D -0.977278
1     A  1.764052     F -0.151357
2     A  1.764052     E  0.950088
3     B  0.400157     D -0.977278
4     B  0.400157     F -0.151357
5     C  0.978738     D -0.977278
6     C  0.978738     F -0.151357
7     C  0.978738     E  0.950088
8     D  2.240893     D -0.977278
9     D  2.240893     F -0.151357
10    D  2.240893     E  0.950088
11    D  2.240893     B  1.867558

left.conditional_join(right, ('value', 'value', '<'))

  left           right
   key     value   key     value
0    A  1.764052     B  1.867558
1    B  0.400157     E  0.950088
2    B  0.400157     B  1.867558
3    C  0.978738     B  1.867558

列作为元组的可变参数传递,每个元组都由左侧数据帧的列、右侧数据帧的列和连接运算符组成,连接运算符可以是任何一个(>、<、>=、<=、!=)。在上面的例子中,返回了一个多索引列,因为列名有重叠。
从性能上来看,这比天真的交叉连接要好。
np.random.seed(0)
dd = pd.DataFrame({'value':np.random.randint(100000, size=50_000)})
df = pd.DataFrame({'start':np.random.randint(100000, size=1_000), 
                   'end':np.random.randint(100000, size=1_000)})

dd.head()

   value
0  68268
1  43567
2  42613
3  45891
4  21243

df.head()

   start    end
0  71915  47005
1  64284  44913
2  13377  96626
3  75823  38673
4  29151    575


%%timeit
out = df.merge(dd, how='cross')
out.loc[(out.start < out.value) & (out.end > out.value)]
5.12 s ± 19 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'))
280 ms ± 5.56 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%timeit df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'), use_numba=True)
124 ms ± 12.4 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

out = df.merge(dd, how='cross')
out = out.loc[(out.start < out.value) & (out.end > out.value)]
A = df.conditional_join(dd, ('start', 'value' ,'<'), ('end', 'value' ,'>'))
columns = A.columns.tolist()
A = A.sort_values(columns, ignore_index = True)
out = out.sort_values(columns, ignore_index = True)

A.equals(out)
True

根据数据大小的不同,当存在等值连接时,您可以获得更好的性能。在这种情况下,使用pandas的merge函数,但最终的数据框将延迟到非等值连接计算完成之后。让我们来看一下这里的数据。
import pandas as pd
import numpy as np
import random
import datetime

def random_dt_bw(start_date,end_date):
    days_between = (end_date - start_date).days
    random_num_days = random.randrange(days_between)
    random_dt = start_date + datetime.timedelta(days=random_num_days)
    return random_dt

def generate_data(n=1000):
    items = [f"i_{x}" for x in range(n)]
    start_dates = [random_dt_bw(datetime.date(2020,1,1),datetime.date(2020,9,1)) for x in range(n)]
    end_dates = [x + datetime.timedelta(days=random.randint(1,10)) for x in start_dates]
    
    offerDf = pd.DataFrame({"Item":items,
                            "StartDt":start_dates,
                            "EndDt":end_dates})
    
    transaction_items = [f"i_{random.randint(0,n)}" for x in range(5*n)]
    transaction_dt = [random_dt_bw(datetime.date(2020,1,1),datetime.date(2020,9,1)) for x in range(5*n)]
    sales_amt = [random.randint(0,1000) for x in range(5*n)]
    
    transactionDf = pd.DataFrame({"Item":transaction_items,"TransactionDt":transaction_dt,"Sales":sales_amt})

    return offerDf,transactionDf

offerDf,transactionDf = generate_data(n=100000)


offerDf = (offerDf
           .assign(StartDt = offerDf.StartDt.astype(np.datetime64), 
                   EndDt = offerDf.EndDt.astype(np.datetime64)
                  )
           )

transactionDf = transactionDf.assign(TransactionDt = transactionDf.TransactionDt.astype(np.datetime64))

# you can get more performance when using ints/datetimes
# in the equi join, compared to strings

offerDf = offerDf.assign(Itemr = offerDf.Item.str[2:].astype(int))

transactionDf = transactionDf.assign(Itemr = transactionDf.Item.str[2:].astype(int))

transactionDf.head()
      Item TransactionDt  Sales  Itemr
0  i_43407    2020-05-29    692  43407
1  i_95044    2020-07-22    964  95044
2  i_94560    2020-01-09    462  94560
3  i_11246    2020-02-26    690  11246
4  i_55974    2020-03-07    219  55974

offerDf.head()
  Item    StartDt      EndDt  Itemr
0  i_0 2020-04-18 2020-04-19      0
1  i_1 2020-02-28 2020-03-07      1
2  i_2 2020-03-28 2020-03-30      2
3  i_3 2020-08-03 2020-08-13      3
4  i_4 2020-05-26 2020-06-04      4

# merge on strings 
merged_df = pd.merge(offerDf,transactionDf,on='Itemr')
classic_int = merged_df[(merged_df['TransactionDt']>=merged_df['StartDt']) &
                        (merged_df['TransactionDt']<=merged_df['EndDt'])]

# merge on ints ... usually faster
merged_df = pd.merge(offerDf,transactionDf,on='Item')
classic_str = merged_df[(merged_df['TransactionDt']>=merged_df['StartDt']) &            
                        (merged_df['TransactionDt']<=merged_df['EndDt'])]

# merge on integers
cond_join_int = (transactionDf
                 .conditional_join(
                     offerDf, 
                     ('Itemr', 'Itemr', '=='), 
                     ('TransactionDt', 'StartDt', '>='), 
                     ('TransactionDt', 'EndDt', '<=')
                  )
                 )

# merge on strings
cond_join_str = (transactionDf
                 .conditional_join(
                     offerDf, 
                     ('Item', 'Item', '=='), 
                     ('TransactionDt', 'StartDt', '>='), 
                     ('TransactionDt', 'EndDt', '<=')
                  )
                )

%%timeit
merged_df = pd.merge(offerDf,transactionDf,on='Item')
classic_str = merged_df[(merged_df['TransactionDt']>=merged_df['StartDt']) &
                        (merged_df['TransactionDt']<=merged_df['EndDt'])]
292 ms ± 3.84 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit
merged_df = pd.merge(offerDf,transactionDf,on='Itemr')
classic_int = merged_df[(merged_df['TransactionDt']>=merged_df['StartDt']) &
                        (merged_df['TransactionDt']<=merged_df['EndDt'])]
253 ms ± 2.7 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit 
(transactionDf
.conditional_join(
    offerDf, 
    ('Item', 'Item', '=='), 
    ('TransactionDt', 'StartDt', '>='), 
    ('TransactionDt', 'EndDt', '<=')
   )
)
256 ms ± 9.66 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

%%timeit 
(transactionDf
.conditional_join(
    offerDf, 
    ('Itemr', 'Itemr', '=='), 
    ('TransactionDt', 'StartDt', '>='), 
    ('TransactionDt', 'EndDt', '<=')
   )
)
71.8 ms ± 2.24 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

# check that both dataframes are equal
cols = ['Item', 'TransactionDt', 'Sales', 'Itemr_y','StartDt', 'EndDt', 'Itemr_x']
cond_join_str = cond_join_str.drop(columns=('right', 'Item')).set_axis(cols, axis=1)

(cond_join_str
.sort_values(cond_join_str.columns.tolist())
.reset_index(drop=True)
.reindex(columns=classic_str.columns)
.equals(
    classic_str
    .sort_values(classic_str.columns.tolist())
    .reset_index(drop=True)
))

True

0

我认为你应该在解释中包含这个内容,因为这是一个我经常看到的相关合并,被称为cross-join。当唯一的数据框没有共享列时,就会发生这种合并,它只是将两个数据框并排合并:

设置:

names1 = [{'A':'Jack', 'B':'Jill'}]

names2 = [{'C':'Tommy', 'D':'Tammy'}]

df1=pd.DataFrame(names1)
df2=pd.DataFrame(names2)
df_merged= pd.merge(df1.assign(X=1), df2.assign(X=1), on='X').drop('X', 1)

这将创建一个虚拟的 X 列,与 X 合并,然后删除它以生成

df_merged:

      A     B      C      D
0  Jack  Jill  Tommy  Tammy

请查看问题下的第二条评论。Cross join最初是其中的一部分(请参考编辑历史记录),但后来因为量太大而被编辑成自己的帖子。 - cs95
2
鉴于“交叉连接”不是本课程的内容,所以是的...不过,我很感谢你的善意贡献 :) - cs95

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